From 1107cb58c05dcf921d83418d7e06b556e0edf16c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 15 Jun 2023 14:28:32 -0400 Subject: [PATCH] LibWeb: Compute some media timeline rects/sizes before painting anything The idea here is to let us decide ahead of time what components to paint depending on the size available. We currently paint each component left- to-right, until we run out of room. This implicitly gives priority to the left-most components. We will soon paint volume controls on the right-side of the timeline. Subjectively, they should have a higher priority than, say, the timeline scrubbing bar (i.e. it's more important to be able to mute audio than to seek). By computing these components before painting, we can more easily allocate sections to the components in priority order, until the area remaining has been depleted. --- .../LibWeb/Painting/MediaPaintable.cpp | 153 +++++++++--------- .../LibWeb/Painting/MediaPaintable.h | 20 ++- 2 files changed, 91 insertions(+), 82 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp index a3c99133c8..d840a6d9c7 100644 --- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp @@ -55,44 +55,69 @@ void MediaPaintable::fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location void MediaPaintable::paint_media_controls(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect, Optional const& mouse_position) const { - auto maximum_control_box_size = context.rounded_device_pixels(40); - auto playback_padding = context.rounded_device_pixels(5); + auto components = compute_control_bar_components(context, media_element, media_rect); + context.painter().fill_rect(components.control_box_rect.to_type(), control_box_color.with_alpha(0xd0)); - auto control_box_rect = media_rect; - if (control_box_rect.height() > maximum_control_box_size) - control_box_rect.take_from_top(control_box_rect.height() - maximum_control_box_size); - - context.painter().fill_rect(control_box_rect.to_type(), control_box_color.with_alpha(0xd0)); - media_element.cached_layout_boxes({}).control_box_rect = context.scale_to_css_rect(control_box_rect); - - control_box_rect = paint_control_bar_playback_button(context, media_element, control_box_rect, mouse_position); - control_box_rect.take_from_left(playback_padding); - - control_box_rect = paint_control_bar_timeline(context, media_element, control_box_rect, mouse_position); - control_box_rect.take_from_left(playback_padding); - - control_box_rect = paint_control_bar_timestamp(context, media_element, control_box_rect); - control_box_rect.take_from_left(playback_padding); + paint_control_bar_playback_button(context, media_element, components, mouse_position); + paint_control_bar_timeline(context, media_element, components, mouse_position); + paint_control_bar_timestamp(context, components); } -DevicePixelRect MediaPaintable::paint_control_bar_playback_button(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect control_box_rect, Optional const& mouse_position) const +MediaPaintable::Components MediaPaintable::compute_control_bar_components(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect) const { - auto maximum_playback_button_size = context.rounded_device_pixels(15); - auto maximum_playback_button_offset_x = context.rounded_device_pixels(15); + auto maximum_control_box_height = context.rounded_device_pixels(40); + auto component_padding = context.rounded_device_pixels(5); - auto playback_button_size = min(maximum_playback_button_size, control_box_rect.height() / 2); - auto playback_button_offset_x = min(maximum_playback_button_offset_x, control_box_rect.width()); - auto playback_button_offset_y = (control_box_rect.height() - playback_button_size) / 2; + Components components {}; - auto playback_button_location = control_box_rect.top_left().translated(playback_button_offset_x, playback_button_offset_y); + components.control_box_rect = media_rect; + if (components.control_box_rect.height() > maximum_control_box_height) + components.control_box_rect.take_from_top(components.control_box_rect.height() - maximum_control_box_height); - auto playback_button_hover_rect = DevicePixelRect { - control_box_rect.top_left(), - { playback_button_size + playback_button_offset_x * 2, control_box_rect.height() } - }; - media_element.cached_layout_boxes({}).playback_button_rect = context.scale_to_css_rect(playback_button_hover_rect); + auto remaining_rect = components.control_box_rect; + remaining_rect.shrink(component_padding * 2, 0); - auto playback_button_is_hovered = mouse_position.has_value() && playback_button_hover_rect.contains(*mouse_position); + auto playback_button_rect_width = min(context.rounded_device_pixels(40), remaining_rect.width()); + components.playback_button_rect = remaining_rect; + components.playback_button_rect.set_width(playback_button_rect_width); + remaining_rect.take_from_left(playback_button_rect_width); + + auto current_time = human_readable_digital_time(round(media_element.current_time())); + auto duration = human_readable_digital_time(isnan(media_element.duration()) ? 0 : round(media_element.duration())); + components.timestamp = String::formatted("{} / {}", current_time, duration).release_value_but_fixme_should_propagate_errors(); + + auto const& scaled_font = layout_node().scaled_font(context); + components.timestamp_font = scaled_font.with_size(10); + if (!components.timestamp_font) + components.timestamp_font = scaled_font; + + auto timestamp_size = DevicePixels { static_cast(ceilf(components.timestamp_font->width(components.timestamp))) }; + if (timestamp_size <= remaining_rect.width()) { + components.timestamp_rect = remaining_rect; + components.timestamp_rect.take_from_left(remaining_rect.width() - timestamp_size); + remaining_rect.take_from_right(timestamp_size + component_padding); + } + + components.timeline_button_size = context.rounded_device_pixels(16); + if ((components.timeline_button_size * 3) <= remaining_rect.width()) + components.timeline_rect = remaining_rect; + + media_element.cached_layout_boxes({}).control_box_rect = context.scale_to_css_rect(components.control_box_rect); + media_element.cached_layout_boxes({}).playback_button_rect = context.scale_to_css_rect(components.playback_button_rect); + media_element.cached_layout_boxes({}).timeline_rect = context.scale_to_css_rect(components.timeline_rect); + + return components; +} + +void MediaPaintable::paint_control_bar_playback_button(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional const& mouse_position) +{ + auto playback_button_size = components.playback_button_rect.width() * 4 / 10; + + auto playback_button_offset_x = (components.playback_button_rect.width() - playback_button_size) / 2; + auto playback_button_offset_y = (components.playback_button_rect.height() - playback_button_size) / 2; + auto playback_button_location = components.playback_button_rect.top_left().translated(playback_button_offset_x, playback_button_offset_y); + + auto playback_button_is_hovered = mouse_position.has_value() && components.playback_button_rect.contains(*mouse_position); auto playback_button_color = control_button_color(playback_button_is_hovered); if (media_element.paused()) { @@ -106,85 +131,55 @@ DevicePixelRect MediaPaintable::paint_control_bar_playback_button(PaintContext& } else { DevicePixelRect pause_button_left_rect { playback_button_location, - { maximum_playback_button_size / 3, playback_button_size } + { playback_button_size / 3, playback_button_size } }; DevicePixelRect pause_button_right_rect { - playback_button_location.translated(maximum_playback_button_size * 2 / 3, 0), - { maximum_playback_button_size / 3, playback_button_size } + playback_button_location.translated(playback_button_size * 2 / 3, 0), + { playback_button_size / 3, playback_button_size } }; context.painter().fill_rect(pause_button_left_rect.to_type(), playback_button_color); context.painter().fill_rect(pause_button_right_rect.to_type(), playback_button_color); } - - control_box_rect.take_from_left(playback_button_hover_rect.width()); - return control_box_rect; } -DevicePixelRect MediaPaintable::paint_control_bar_timeline(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect control_box_rect, Optional const& mouse_position) const +void MediaPaintable::paint_control_bar_timeline(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional const& mouse_position) { - auto maximum_timeline_button_size = context.rounded_device_pixels(16); + if (components.timeline_rect.is_empty()) + return; - auto timeline_rect = control_box_rect; - if (is(media_element)) - timeline_rect.set_width(min(control_box_rect.width() * 6 / 10, timeline_rect.width() * 4 / 10)); - else - timeline_rect.set_width(min(control_box_rect.width() * 6 / 10, timeline_rect.width())); - media_element.cached_layout_boxes({}).timeline_rect = context.scale_to_css_rect(timeline_rect); + auto timelime_scrub_rect = components.timeline_rect; + timelime_scrub_rect.shrink(components.timeline_button_size, timelime_scrub_rect.height() - components.timeline_button_size / 2); auto playback_percentage = isnan(media_element.duration()) ? 0.0 : media_element.current_time() / media_element.duration(); - auto playback_position = static_cast(static_cast(timeline_rect.width())) * playback_percentage; - - auto timeline_button_size = min(maximum_timeline_button_size, timeline_rect.height() / 2); + auto playback_position = static_cast(static_cast(timelime_scrub_rect.width())) * playback_percentage; auto timeline_button_offset_x = static_cast(round(playback_position)); Gfx::AntiAliasingPainter painter { context.painter() }; - auto playback_timelime_scrub_rect = timeline_rect; - playback_timelime_scrub_rect.shrink(0, timeline_rect.height() - timeline_button_size / 2); - - auto timeline_past_rect = playback_timelime_scrub_rect; + auto timeline_past_rect = timelime_scrub_rect; timeline_past_rect.set_width(timeline_button_offset_x); painter.fill_rect_with_rounded_corners(timeline_past_rect.to_type(), control_highlight_color.lightened(), 4); - auto timeline_future_rect = playback_timelime_scrub_rect; + auto timeline_future_rect = timelime_scrub_rect; timeline_future_rect.take_from_left(timeline_button_offset_x); painter.fill_rect_with_rounded_corners(timeline_future_rect.to_type(), Color::Black, 4); - auto timeline_button_rect = timeline_rect; - timeline_button_rect.shrink(timeline_rect.width() - timeline_button_size, timeline_rect.height() - timeline_button_size); - timeline_button_rect.set_x(timeline_rect.x() + timeline_button_offset_x - timeline_button_size / 2); + auto timeline_button_rect = timelime_scrub_rect; + timeline_button_rect.shrink(timelime_scrub_rect.width() - components.timeline_button_size, timelime_scrub_rect.height() - components.timeline_button_size); + timeline_button_rect.set_x(timelime_scrub_rect.x() + timeline_button_offset_x - components.timeline_button_size / 2); - auto timeline_is_hovered = mouse_position.has_value() && timeline_rect.contains(*mouse_position); + auto timeline_is_hovered = mouse_position.has_value() && components.timeline_rect.contains(*mouse_position); auto timeline_color = control_button_color(timeline_is_hovered); painter.fill_ellipse(timeline_button_rect.to_type(), timeline_color); - - control_box_rect.take_from_left(timeline_rect.width() + timeline_button_size / 2); - return control_box_rect; } -DevicePixelRect MediaPaintable::paint_control_bar_timestamp(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect control_box_rect) const +void MediaPaintable::paint_control_bar_timestamp(PaintContext& context, Components const& components) { - auto current_time = human_readable_digital_time(round(media_element.current_time())); - auto duration = human_readable_digital_time(isnan(media_element.duration()) ? 0 : round(media_element.duration())); + if (components.timestamp_rect.is_empty()) + return; - auto timestamp = String::formatted("{} / {}", current_time, duration).release_value_but_fixme_should_propagate_errors(); - - auto const& scaled_font = layout_node().scaled_font(context); - auto font = scaled_font.with_size(10); - if (!font) - font = scaled_font; - - auto timestamp_size = static_cast(ceilf(font->width(timestamp))); - if (timestamp_size > control_box_rect.width()) - return control_box_rect; - - auto timestamp_rect = control_box_rect; - timestamp_rect.set_width(timestamp_size); - context.painter().draw_text(timestamp_rect.to_type(), timestamp, *font, Gfx::TextAlignment::CenterLeft, Color::White); - - control_box_rect.take_from_left(timestamp_rect.width()); - return control_box_rect; + context.painter().draw_text(components.timestamp_rect.to_type(), components.timestamp, *components.timestamp_font, Gfx::TextAlignment::CenterLeft, Color::White); } MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badge, CSSPixelPoint position, unsigned button, unsigned) diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h index fcdcd67c29..96d2309b31 100644 --- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h @@ -8,6 +8,7 @@ #include #include +#include namespace Web::Painting { @@ -23,13 +24,26 @@ protected: void paint_media_controls(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect media_rect, Optional const& mouse_position) const; private: + struct Components { + DevicePixelRect control_box_rect; + DevicePixelRect playback_button_rect; + + DevicePixelRect timeline_rect; + DevicePixels timeline_button_size; + + String timestamp; + RefPtr timestamp_font; + DevicePixelRect timestamp_rect; + }; + virtual bool wants_mouse_events() const override { return true; } virtual DispatchEventOfSameName handle_mouseup(Badge, CSSPixelPoint, unsigned button, unsigned modifiers) override; virtual DispatchEventOfSameName handle_mousemove(Badge, CSSPixelPoint, unsigned buttons, unsigned modifiers) override; - DevicePixelRect paint_control_bar_playback_button(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect control_box_rect, Optional const& mouse_position) const; - DevicePixelRect paint_control_bar_timeline(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect control_box_rect, Optional const& mouse_position) const; - DevicePixelRect paint_control_bar_timestamp(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect control_box_rect) const; + Components compute_control_bar_components(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect media_rect) const; + static void paint_control_bar_playback_button(PaintContext&, HTML::HTMLMediaElement const&, Components const&, Optional const& mouse_position); + static void paint_control_bar_timeline(PaintContext&, HTML::HTMLMediaElement const&, Components const&, Optional const& mouse_position); + static void paint_control_bar_timestamp(PaintContext&, Components const&); }; }