menu_view: use timer for animation timing

This patch removes the use of sync signals as time source. The animation
phase is now timed using a timer connection as tick source while sync
signals are used for scheduling the redraws.

Issue #5347
This commit is contained in:
Norman Feske 2024-09-19 15:55:57 +02:00 committed by Christian Helmuth
parent ffcd08b5c7
commit 58d20c7751
7 changed files with 57 additions and 114 deletions

View File

@ -85,7 +85,7 @@ struct Menu_view::Button_widget : Widget, Animator::Item
* or changing the selection state of a button, the transition
* must be quick to provide a responsive feel.
*/
enum { SLOW = 80, MEDIUM = 40, FAST = 3 };
enum { SLOW = 40, MEDIUM = 20, FAST = 2 };
int steps = SLOW;
if (_hovered && !new_hovered) steps = MEDIUM;
if (!_hovered && new_hovered) steps = FAST;

View File

@ -122,7 +122,7 @@ class Menu_view::Cursor : List_model<Cursor>::Element
void update(Xml_node const &node)
{
_move_to(_position_from_xml_node(node), Steps{12});
_move_to(_position_from_xml_node(node), Steps{6});
}
};

View File

@ -47,9 +47,9 @@ struct Menu_view::Dialog : List_model<Dialog>::Element
struct Action : Interface
{
virtual void trigger_redraw() = 0;
virtual void hover_changed() = 0;
virtual void observed_seq_number(Input::Seq_number) = 0;
virtual Ticks now() = 0;
};
Action &_action;
@ -72,6 +72,15 @@ struct Menu_view::Dialog : List_model<Dialog>::Element
void _handle_input();
Signal_handler<Dialog> _gui_sync_handler {
_env.ep(), *this, &Dialog::_handle_gui_sync };
void _handle_gui_sync();
bool _gui_sync_enabled = false;
Ticks _previous_sync { };
Constructible<Gui_buffer> _buffer { };
Gui::View_ref _view_ref { };
@ -151,7 +160,7 @@ struct Menu_view::Dialog : List_model<Dialog>::Element
_root_widget.gen_hover_model(xml, _hovered_position);
}
void redraw()
void _redraw()
{
if (!_redraw_scheduled)
return;
@ -190,7 +199,7 @@ struct Menu_view::Dialog : List_model<Dialog>::Element
bool hovered() const { return _hovered; }
void animate()
void _animate()
{
bool const progress = _local_animator.active();
@ -200,9 +209,14 @@ struct Menu_view::Dialog : List_model<Dialog>::Element
_redraw_scheduled = true;
}
bool animation_in_progress() const { return _local_animator.active(); }
void enforce_font_sytle_change()
{
_handle_dialog();
bool redraw_scheduled() const { return _redraw_scheduled; }
/* fast-forward geometry animation */
while (_local_animator.active())
_animate();
}
/*
* List_model
@ -237,7 +251,13 @@ void Menu_view::Dialog::_handle_dialog()
_redraw_scheduled = true;
_action.hover_changed();
_action.trigger_redraw();
if (!_gui_sync_enabled) {
_gui.framebuffer.sync_sigh(_gui_sync_handler);
_gui_sync_enabled = true;
}
_handle_gui_sync();
}
@ -287,4 +307,25 @@ void Menu_view::Dialog::_handle_input()
_action.hover_changed();
}
void Menu_view::Dialog::_handle_gui_sync()
{
Ticks const now = _action.now();
Ticks const passed_ticks { now.cs - _previous_sync.cs };
for (unsigned i = 0; i < passed_ticks.cs; i++)
_animate();
if (passed_ticks.cs)
_redraw();
/* deactivate sync signalling when idle */
if (_gui_sync_enabled && !_local_animator.active()) {
_gui.framebuffer.sync_sigh(Signal_context_capability());
_gui_sync_enabled = false;
}
_previous_sync = now;
}
#endif /* _DIALOG_H_ */

View File

@ -89,7 +89,7 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position
_hover = node.attribute_value("hover", false);
_factory.styles.with_label_style(node, [&] (Label_style style) {
_color.fade_to(style.color, Animated_color::Steps{80}); });
_color.fade_to(style.color, Animated_color::Steps{40}); });
if (node.has_attribute("text")) {
_text = node.attribute_value("text", _text);

View File

@ -83,45 +83,14 @@ struct Menu_view::Main : Dialog::Action
} _input_seq_number { };
struct Frame { uint64_t count; };
/*
* Timer used for animating widgets
*/
struct Frame_timer : Timer::Connection
{
enum { PERIOD = 10 };
Frame curr_frame() const { return { elapsed_ms() / PERIOD }; }
void schedule() { trigger_once((Genode::uint64_t)Frame_timer::PERIOD*1000); }
Frame_timer(Env &env) : Timer::Connection(env) { }
} _timer { _env };
void _handle_frame_timer();
Timer::Connection _timer { _env };
/**
* Dialog::Action
*/
void trigger_redraw() override
Ticks now() override
{
/*
* If we have not processed a period for at least one frame, perform the
* processing immediately. This way, we avoid latencies when the dialog
* model is updated sporadically.
*/
Frame const curr_frame = _timer.curr_frame();
if (curr_frame.count != _last_frame.count) {
if (curr_frame.count - _last_frame.count > 10)
_last_frame = curr_frame;
_handle_frame_timer();
} else {
_timer.schedule();
}
return { .cs = _timer.curr_time().trunc_to_plain_ms().value / 10 };
}
/**
@ -137,38 +106,16 @@ struct Menu_view::Main : Dialog::Action
_input_seq_number.update(seq);
}
Signal_handler<Main> _frame_timer_handler = {
_env.ep(), *this, &Main::_handle_frame_timer};
Constructible<Genode::Expanding_reporter> _hover_reporter { };
void _update_hover_report();
/**
* Frame of last call of 'handle_frame_timer'
*/
Frame _last_frame { };
/**
* Number of frames between two redraws
*/
static constexpr unsigned REDRAW_PERIOD = 2;
/**
* Counter used for triggering redraws. Incremented in each frame-timer
* period, wraps at 'REDRAW_PERIOD'. The redraw is performed when the
* counter wraps.
*/
unsigned _frame_cnt = 0;
Main(Env &env, Vfs::Env &libc_vfs_env)
:
_env(env), _vfs_env(libc_vfs_env)
{
_config.sigh(_config_handler);
_config_handler.local_submit(); /* apply initial configuration */
_timer.sigh(_frame_timer_handler);
}
};
@ -239,57 +186,10 @@ void Menu_view::Main::_handle_config()
/* re-assign font pointers in labels (needed due to font style change) */
if (!_styles.up_to_date()) {
_dialogs.for_each([&] (Dialog &dialog) {
dialog._handle_dialog();
/* fast-forward geometry animation on font changes */
while (dialog.animation_in_progress())
dialog.animate();
});
dialog.enforce_font_sytle_change(); });
_styles.flush_outdated_styles();
}
trigger_redraw();
}
void Menu_view::Main::_handle_frame_timer()
{
_frame_cnt++;
Frame const curr_frame = _timer.curr_frame();
unsigned const passed_frames =
max(unsigned(curr_frame.count - _last_frame.count), 4U);
if (passed_frames > 0)
_dialogs.for_each([&] (Dialog &dialog) {
for (unsigned i = 0; i < passed_frames; i++)
dialog.animate(); });
bool any_redraw_scheduled = false;
_dialogs.for_each([&] (Dialog const &dialog) {
any_redraw_scheduled |= dialog.redraw_scheduled(); });
_last_frame = curr_frame;
bool const redraw_skipped = any_redraw_scheduled && (_frame_cnt < REDRAW_PERIOD);
if (!redraw_skipped) {
_frame_cnt = 0;
_dialogs.for_each([&] (Dialog &dialog) {
dialog.redraw(); });
}
bool any_animation_in_progress = false;
_dialogs.for_each([&] (Dialog const &dialog) {
any_animation_in_progress |= dialog.animation_in_progress(); });
/*
* Deactivate timer periods when idle, activate timer when an animation is
* in progress or a redraw is pending.
*/
if (any_animation_in_progress || redraw_skipped)
_timer.schedule();
}

View File

@ -36,6 +36,8 @@ namespace Menu_view {
using Point = Surface_base::Point;
using Area = Surface_base::Area;
using Rect = Surface_base::Rect;
struct Ticks { uint64_t cs; /* centi-seconds (10 ms) */ };
}
#endif /* _TYPES_H_ */

View File

@ -110,7 +110,7 @@ class Menu_view::Widget : List_model<Widget>::Element
return node.attribute_value("version", Version());
}
static Animated_rect::Steps motion_steps() { return { 60 }; };
static Animated_rect::Steps motion_steps() { return { 30 }; };
protected: