From ee48d7514fe6775af1338a792df49da7b46a48ed Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 12 Jun 2023 08:21:35 -0400 Subject: [PATCH] LibWeb: Extract media element timeline painting to a base class This moves the painting of the media timeout out of VideoPaintable into a base MediaPaintable. This is to allow re-using the same timeline logic and controls for audio elements. --- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + Userland/Libraries/LibWeb/Forward.h | 1 + .../Libraries/LibWeb/HTML/HTMLMediaElement.h | 17 ++ .../Libraries/LibWeb/HTML/HTMLVideoElement.h | 16 -- .../LibWeb/Painting/MediaPaintable.cpp | 245 ++++++++++++++++++ .../LibWeb/Painting/MediaPaintable.h | 35 +++ .../LibWeb/Painting/VideoPaintable.cpp | 224 +--------------- .../LibWeb/Painting/VideoPaintable.h | 15 +- 8 files changed, 314 insertions(+), 240 deletions(-) create mode 100644 Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp create mode 100644 Userland/Libraries/LibWeb/Painting/MediaPaintable.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 0576fff3e2..08266fcc02 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -454,6 +454,7 @@ set(SOURCES Painting/InlinePaintable.cpp Painting/LabelablePaintable.cpp Painting/MarkerPaintable.cpp + Painting/MediaPaintable.cpp Painting/NestedBrowsingContextPaintable.cpp Painting/PaintContext.cpp Painting/Paintable.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 89b27f7701..7421de5325 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -485,6 +485,7 @@ namespace Web::Painting { class ButtonPaintable; class CheckBoxPaintable; class LabelablePaintable; +class MediaPaintable; class Paintable; class PaintableBox; class PaintableWithLines; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h index 5484d484ec..75af877a06 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h @@ -8,14 +8,17 @@ #pragma once #include +#include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -84,6 +87,16 @@ public: JS::NonnullGCPtr video_tracks() const { return *m_video_tracks; } + void set_layout_mouse_position(Badge, Optional mouse_position) { m_mouse_position = move(mouse_position); } + Optional const& layout_mouse_position(Badge) const { return m_mouse_position; } + + struct CachedLayoutBoxes { + Optional control_box_rect; + Optional playback_button_rect; + Optional timeline_rect; + }; + CachedLayoutBoxes& cached_layout_boxes(Badge) const { return m_layout_boxes; } + protected: HTMLMediaElement(DOM::Document&, DOM::QualifiedName); @@ -220,6 +233,10 @@ private: JS::GCPtr m_fetch_controller; bool m_seek_in_progress = false; + + // Cached state for layout. + Optional m_mouse_position; + mutable CachedLayoutBoxes m_layout_boxes; }; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h index 3b06af6001..d6a9955969 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.h @@ -8,11 +8,9 @@ #include #include -#include #include #include #include -#include #include namespace Web::HTML { @@ -43,16 +41,6 @@ public: VideoFrame const& current_frame() const { return m_current_frame; } RefPtr const& poster_frame() const { return m_poster_frame; } - void set_layout_mouse_position(Badge, Optional mouse_position) { m_mouse_position = move(mouse_position); } - Optional const& layout_mouse_position(Badge) const { return m_mouse_position; } - - struct CachedLayoutBoxes { - Optional control_box_rect; - Optional playback_button_rect; - Optional timeline_rect; - }; - CachedLayoutBoxes& cached_layout_boxes(Badge) const { return m_layout_boxes; } - private: HTMLVideoElement(DOM::Document&, DOM::QualifiedName); @@ -79,10 +67,6 @@ private: JS::GCPtr m_fetch_controller; Optional m_load_event_delayer; - - // Cached state for layout - Optional m_mouse_position; - mutable CachedLayoutBoxes m_layout_boxes; }; } diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp new file mode 100644 index 0000000000..4b35a1b6d3 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +static constexpr auto control_box_color = Gfx::Color::from_rgb(0x26'26'26); +static constexpr auto control_highlight_color = Gfx::Color::from_rgb(0x1d'99'f3); + +static constexpr Gfx::Color control_button_color(bool is_hovered) +{ + if (!is_hovered) + return Color::White; + return control_highlight_color; +} + +MediaPaintable::MediaPaintable(Layout::ReplacedBox const& layout_box) + : PaintableBox(layout_box) +{ +} + +Optional MediaPaintable::mouse_position(PaintContext& context, HTML::HTMLMediaElement const& media_element) +{ + auto const& layout_mouse_position = media_element.layout_mouse_position({}); + + if (layout_mouse_position.has_value() && media_element.document().hovered_node() == &media_element) + return context.rounded_device_point(*layout_mouse_position); + + return {}; +} + +void MediaPaintable::fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location, Array coordinates, Color color) +{ + Gfx::AntiAliasingPainter aa_painter { painter }; + Gfx::Path path; + path.move_to((coordinates[0] + location).to_type()); + path.line_to((coordinates[1] + location).to_type()); + path.line_to((coordinates[2] + location).to_type()); + path.close(); + aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); +} + +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 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); +} + +DevicePixelRect MediaPaintable::paint_control_bar_playback_button(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect control_box_rect, Optional const& mouse_position) const +{ + auto maximum_playback_button_size = context.rounded_device_pixels(15); + auto maximum_playback_button_offset_x = context.rounded_device_pixels(15); + + 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; + + auto playback_button_location = control_box_rect.top_left().translated(playback_button_offset_x, playback_button_offset_y); + + 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 playback_button_is_hovered = mouse_position.has_value() && playback_button_hover_rect.contains(*mouse_position); + auto playback_button_color = control_button_color(playback_button_is_hovered); + + if (media_element.paused()) { + Array play_button_coordinates { { + { 0, 0 }, + { static_cast(playback_button_size), static_cast(playback_button_size) / 2 }, + { 0, static_cast(playback_button_size) }, + } }; + + fill_triangle(context.painter(), playback_button_location.to_type(), play_button_coordinates, playback_button_color); + } else { + DevicePixelRect pause_button_left_rect { + playback_button_location, + { maximum_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 } + }; + + 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 +{ + auto maximum_timeline_button_size = context.rounded_device_pixels(16); + + 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 playback_percentage = 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 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; + 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; + 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_is_hovered = mouse_position.has_value() && 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 +{ + auto current_time = human_readable_digital_time(round(media_element.current_time())); + auto duration = human_readable_digital_time(round(media_element.duration())); + auto timestamp = String::formatted("{} / {}", current_time, duration).release_value_but_fixme_should_propagate_errors(); + + auto timestamp_size = static_cast(ceilf(context.painter().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); + + auto const& scaled_font = layout_node().scaled_font(context); + context.painter().draw_text(timestamp_rect.to_type(), timestamp, scaled_font, Gfx::TextAlignment::CenterLeft, Color::White); + + control_box_rect.take_from_left(timestamp_rect.width()); + return control_box_rect; +} + +MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badge, CSSPixelPoint position, unsigned button, unsigned) +{ + if (button != GUI::MouseButton::Primary) + return DispatchEventOfSameName::Yes; + + auto& media_element = *verify_cast(layout_box().dom_node()); + auto const& cached_layout_boxes = media_element.cached_layout_boxes({}); + + // FIXME: This runs from outside the context of any user script, so we do not have a running execution + // context. This pushes one to allow the promise creation hook to run. + auto& environment_settings = document().relevant_settings_object(); + environment_settings.prepare_to_run_script(); + + ScopeGuard guard { [&] { environment_settings.clean_up_after_running_script(); } }; + + auto toggle_playback = [&]() -> WebIDL::ExceptionOr { + if (media_element.paused()) + TRY(media_element.play()); + else + TRY(media_element.pause()); + return {}; + }; + + if (cached_layout_boxes.control_box_rect.has_value() && cached_layout_boxes.control_box_rect->contains(position)) { + if (cached_layout_boxes.playback_button_rect.has_value() && cached_layout_boxes.playback_button_rect->contains(position)) { + toggle_playback().release_value_but_fixme_should_propagate_errors(); + return DispatchEventOfSameName::Yes; + } + + if (cached_layout_boxes.timeline_rect.has_value() && cached_layout_boxes.timeline_rect->contains(position)) { + auto x_offset = position.x() - cached_layout_boxes.timeline_rect->x(); + auto x_percentage = static_cast(x_offset) / static_cast(cached_layout_boxes.timeline_rect->width()); + + auto position = static_cast(x_percentage) * media_element.duration(); + media_element.set_current_time(position); + + return DispatchEventOfSameName::Yes; + } + + return DispatchEventOfSameName::No; + } + + toggle_playback().release_value_but_fixme_should_propagate_errors(); + return DispatchEventOfSameName::Yes; +} + +MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mousemove(Badge, CSSPixelPoint position, unsigned, unsigned) +{ + auto& media_element = *verify_cast(layout_box().dom_node()); + + if (absolute_rect().contains(position)) { + media_element.set_layout_mouse_position({}, position); + return DispatchEventOfSameName::Yes; + } + + media_element.set_layout_mouse_position({}, {}); + return DispatchEventOfSameName::No; +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h new file mode 100644 index 0000000000..fcdcd67c29 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +class MediaPaintable : public PaintableBox { + JS_CELL(MediaPaintable, PaintableBox); + +protected: + explicit MediaPaintable(Layout::ReplacedBox const&); + + static Optional mouse_position(PaintContext&, HTML::HTMLMediaElement const&); + static void fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location, Array coordinates, Color color); + + void paint_media_controls(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect media_rect, Optional const& mouse_position) const; + +private: + 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; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp b/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp index ff81add6dc..5c1dd77e18 100644 --- a/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include #include @@ -34,7 +33,7 @@ JS::NonnullGCPtr VideoPaintable::create(Layout::VideoBox const& } VideoPaintable::VideoPaintable(Layout::VideoBox const& layout_box) - : PaintableBox(layout_box) + : MediaPaintable(layout_box) { } @@ -57,7 +56,7 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const if (is_out_of_view(context)) return; - PaintableBox::paint(context, phase); + Base::paint(context, phase); if (phase != PaintPhase::Foreground) return; @@ -68,11 +67,7 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const ScopedCornerRadiusClip corner_clip { context, context.painter(), video_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto const& video_element = layout_box().dom_node(); - auto const& layout_mouse_position = video_element.layout_mouse_position({}); - - Optional mouse_position; - if (layout_mouse_position.has_value() && document().hovered_node() == &video_element) - mouse_position = context.rounded_device_point(*layout_mouse_position); + auto mouse_position = MediaPaintable::mouse_position(context, video_element); auto const& current_frame = video_element.current_frame(); auto const& poster_frame = video_element.poster_frame(); @@ -144,6 +139,14 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const context.painter().fill_rect(video_rect.to_type(), transparent_black); }; + auto paint_loaded_video_controls = [&]() { + auto is_hovered = document().hovered_node() == &video_element; + auto is_paused = video_element.paused(); + + if (is_hovered || is_paused) + paint_media_controls(context, video_element, video_rect, mouse_position); + }; + auto paint_user_agent_controls = video_element.has_attribute(HTML::AttributeNames::controls) || video_element.is_scripting_disabled(); switch (representation) { @@ -155,7 +158,7 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const if (current_frame.frame) paint_frame(current_frame.frame); if (paint_user_agent_controls) - paint_loaded_video_controls(context, video_element, video_rect, mouse_position); + paint_loaded_video_controls(); break; case Representation::PosterFrame: @@ -176,150 +179,6 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const } } -void VideoPaintable::paint_loaded_video_controls(PaintContext& context, HTML::HTMLVideoElement const& video_element, DevicePixelRect video_rect, Optional const& mouse_position) const -{ - auto maximum_control_box_size = context.rounded_device_pixels(30); - auto playback_padding = context.rounded_device_pixels(5); - - auto is_hovered = document().hovered_node() == &video_element; - auto is_paused = video_element.paused(); - - if (!is_hovered && !is_paused) - return; - - auto control_box_rect = video_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)); - layout_box().dom_node().cached_layout_boxes({}).control_box_rect = context.scale_to_css_rect(control_box_rect); - - control_box_rect = paint_control_bar_playback_button(context, video_element, control_box_rect, mouse_position); - control_box_rect.take_from_left(playback_padding); - - control_box_rect = paint_control_bar_timeline(context, video_element, control_box_rect, mouse_position); - control_box_rect.take_from_left(playback_padding); - - control_box_rect = paint_control_bar_timestamp(context, video_element, control_box_rect); - control_box_rect.take_from_left(playback_padding); -} - -static void fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location, Array coordinates, Color color) -{ - Gfx::AntiAliasingPainter aa_painter { painter }; - Gfx::Path path; - path.move_to((coordinates[0] + location).to_type()); - path.line_to((coordinates[1] + location).to_type()); - path.line_to((coordinates[2] + location).to_type()); - path.close(); - aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); -} - -DevicePixelRect VideoPaintable::paint_control_bar_playback_button(PaintContext& context, HTML::HTMLVideoElement const& video_element, DevicePixelRect control_box_rect, Optional const& mouse_position) const -{ - auto maximum_playback_button_size = context.rounded_device_pixels(15); - auto maximum_playback_button_offset_x = context.rounded_device_pixels(15); - - 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; - - auto playback_button_location = control_box_rect.top_left().translated(playback_button_offset_x, playback_button_offset_y); - - auto playback_button_hover_rect = DevicePixelRect { - control_box_rect.top_left(), - { playback_button_size + playback_button_offset_x * 2, control_box_rect.height() } - }; - layout_box().dom_node().cached_layout_boxes({}).playback_button_rect = context.scale_to_css_rect(playback_button_hover_rect); - - auto playback_button_is_hovered = mouse_position.has_value() && playback_button_hover_rect.contains(*mouse_position); - auto playback_button_color = control_button_color(playback_button_is_hovered); - - if (video_element.paused()) { - Array play_button_coordinates { { - { 0, 0 }, - { static_cast(playback_button_size), static_cast(playback_button_size) / 2 }, - { 0, static_cast(playback_button_size) }, - } }; - - fill_triangle(context.painter(), playback_button_location.to_type(), play_button_coordinates, playback_button_color); - } else { - DevicePixelRect pause_button_left_rect { - playback_button_location, - { maximum_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 } - }; - - 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 VideoPaintable::paint_control_bar_timeline(PaintContext& context, HTML::HTMLVideoElement const& video_element, DevicePixelRect control_box_rect, Optional const& mouse_position) const -{ - auto maximum_timeline_button_size = context.rounded_device_pixels(16); - - auto timeline_rect = control_box_rect; - timeline_rect.set_width(min(control_box_rect.width() * 6 / 10, timeline_rect.width())); - layout_box().dom_node().cached_layout_boxes({}).timeline_rect = context.scale_to_css_rect(timeline_rect); - - auto playback_percentage = video_element.current_time() / video_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 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; - 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; - 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_is_hovered = mouse_position.has_value() && 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 VideoPaintable::paint_control_bar_timestamp(PaintContext& context, HTML::HTMLVideoElement const& video_element, DevicePixelRect control_box_rect) const -{ - auto current_time = human_readable_digital_time(round(video_element.current_time())); - auto duration = human_readable_digital_time(round(video_element.duration())); - auto timestamp = String::formatted("{} / {}", current_time, duration).release_value_but_fixme_should_propagate_errors(); - - auto timestamp_size = static_cast(ceilf(context.painter().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); - - auto const& scaled_font = layout_node().scaled_font(context); - context.painter().draw_text(timestamp_rect.to_type(), timestamp, scaled_font, Gfx::TextAlignment::CenterLeft, Color::White); - - control_box_rect.take_from_left(timestamp_rect.width()); - return control_box_rect; -} - void VideoPaintable::paint_placeholder_video_controls(PaintContext& context, DevicePixelRect video_rect, Optional const& mouse_position) const { auto maximum_control_box_size = context.rounded_device_pixels(100); @@ -358,63 +217,4 @@ void VideoPaintable::paint_placeholder_video_controls(PaintContext& context, Dev fill_triangle(context.painter(), playback_button_location.to_type(), play_button_coordinates, playback_button_color); } -VideoPaintable::DispatchEventOfSameName VideoPaintable::handle_mouseup(Badge, CSSPixelPoint position, unsigned button, unsigned) -{ - if (button != GUI::MouseButton::Primary) - return DispatchEventOfSameName::Yes; - - auto& video_element = layout_box().dom_node(); - auto const& cached_layout_boxes = video_element.cached_layout_boxes({}); - - // FIXME: This runs from outside the context of any user script, so we do not have a running execution - // context. This pushes one to allow the promise creation hook to run. - auto& environment_settings = document().relevant_settings_object(); - environment_settings.prepare_to_run_script(); - - ScopeGuard guard { [&] { environment_settings.clean_up_after_running_script(); } }; - - auto toggle_playback = [&]() -> WebIDL::ExceptionOr { - if (video_element.paused()) - TRY(video_element.play()); - else - TRY(video_element.pause()); - return {}; - }; - - if (cached_layout_boxes.control_box_rect.has_value() && cached_layout_boxes.control_box_rect->contains(position)) { - if (cached_layout_boxes.playback_button_rect.has_value() && cached_layout_boxes.playback_button_rect->contains(position)) { - toggle_playback().release_value_but_fixme_should_propagate_errors(); - return DispatchEventOfSameName::Yes; - } - - if (cached_layout_boxes.timeline_rect.has_value() && cached_layout_boxes.timeline_rect->contains(position)) { - auto x_offset = position.x() - cached_layout_boxes.timeline_rect->x(); - auto x_percentage = static_cast(x_offset) / static_cast(cached_layout_boxes.timeline_rect->width()); - - auto position = static_cast(x_percentage) * video_element.duration(); - video_element.set_current_time(position); - - return DispatchEventOfSameName::Yes; - } - - return DispatchEventOfSameName::No; - } - - toggle_playback().release_value_but_fixme_should_propagate_errors(); - return DispatchEventOfSameName::Yes; -} - -VideoPaintable::DispatchEventOfSameName VideoPaintable::handle_mousemove(Badge, CSSPixelPoint position, unsigned, unsigned) -{ - auto& video_element = layout_box().dom_node(); - - if (absolute_rect().contains(position)) { - video_element.set_layout_mouse_position({}, position); - return DispatchEventOfSameName::Yes; - } - - video_element.set_layout_mouse_position({}, {}); - return DispatchEventOfSameName::No; -} - } diff --git a/Userland/Libraries/LibWeb/Painting/VideoPaintable.h b/Userland/Libraries/LibWeb/Painting/VideoPaintable.h index f4b5dea302..511e4122c3 100644 --- a/Userland/Libraries/LibWeb/Painting/VideoPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/VideoPaintable.h @@ -7,12 +7,12 @@ #pragma once #include -#include +#include namespace Web::Painting { -class VideoPaintable final : public PaintableBox { - JS_CELL(VideoPaintable, PaintableBox); +class VideoPaintable final : public MediaPaintable { + JS_CELL(VideoPaintable, MediaPaintable); public: static JS::NonnullGCPtr create(Layout::VideoBox const&); @@ -25,15 +25,6 @@ public: private: VideoPaintable(Layout::VideoBox const&); - 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; - - void paint_loaded_video_controls(PaintContext&, HTML::HTMLVideoElement const&, DevicePixelRect video_rect, Optional const& mouse_position) const; - DevicePixelRect paint_control_bar_playback_button(PaintContext&, HTML::HTMLVideoElement const&, DevicePixelRect control_box_rect, Optional const& mouse_position) const; - DevicePixelRect paint_control_bar_timeline(PaintContext&, HTML::HTMLVideoElement const&, DevicePixelRect control_box_rect, Optional const& mouse_position) const; - DevicePixelRect paint_control_bar_timestamp(PaintContext&, HTML::HTMLVideoElement const&, DevicePixelRect control_box_rect) const; - void paint_placeholder_video_controls(PaintContext&, DevicePixelRect video_rect, Optional const& mouse_position) const; };