mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-08 05:27:14 +09:00

This allows the media paintable to be redrawn when the media element is paused. We change the color of some components on hover, but if the media was paused, that would not be rendered. This wasn't an issue while the media is playing because the update to the timeline would take care of redrawing the paintable.
450 lines
23 KiB
C++
450 lines
23 KiB
C++
/*
|
|
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Array.h>
|
|
#include <AK/NumberFormat.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/HTML/BrowsingContext.h>
|
|
#include <LibWeb/HTML/HTMLAudioElement.h>
|
|
#include <LibWeb/HTML/HTMLVideoElement.h>
|
|
#include <LibWeb/Layout/ReplacedBox.h>
|
|
#include <LibWeb/Page/EventHandler.h>
|
|
#include <LibWeb/Page/Page.h>
|
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
|
#include <LibWeb/Painting/MediaPaintable.h>
|
|
#include <LibWeb/UIEvents/MouseButton.h>
|
|
|
|
namespace Web::Painting {
|
|
|
|
static constexpr auto CONTROL_BOX_COLOR = Gfx::Color::from_rgb(0x26'26'26);
|
|
static constexpr auto CONTROL_BUTTON_COLOR = Gfx::Color::branded_color(Gfx::Color::BrandedColor::Violet);
|
|
static constexpr auto CONTROL_HIGHLIGHT_COLOR = Gfx::Color::branded_color(Gfx::Color::BrandedColor::Violet60);
|
|
|
|
static constexpr Gfx::Color control_button_color(bool is_hovered)
|
|
{
|
|
if (!is_hovered)
|
|
return Color::White;
|
|
return CONTROL_BUTTON_COLOR;
|
|
}
|
|
|
|
MediaPaintable::MediaPaintable(Layout::ReplacedBox const& layout_box)
|
|
: PaintableBox(layout_box)
|
|
{
|
|
}
|
|
|
|
Optional<DevicePixelPoint> 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(DisplayListRecorder& painter, Gfx::IntPoint location, Array<Gfx::IntPoint, 3> coordinates, Color color)
|
|
{
|
|
Gfx::Path path;
|
|
path.move_to((coordinates[0] + location).to_type<float>());
|
|
path.line_to((coordinates[1] + location).to_type<float>());
|
|
path.line_to((coordinates[2] + location).to_type<float>());
|
|
path.close();
|
|
painter.fill_path({
|
|
.path = path,
|
|
.color = color,
|
|
.winding_rule = Gfx::WindingRule::EvenOdd,
|
|
});
|
|
}
|
|
|
|
void MediaPaintable::paint_media_controls(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect, Optional<DevicePixelPoint> const& mouse_position) const
|
|
{
|
|
auto components = compute_control_bar_components(context, media_element, media_rect);
|
|
context.display_list_recorder().fill_rect(components.control_box_rect.to_type<int>(), CONTROL_BOX_COLOR.with_alpha(0xd0));
|
|
|
|
paint_control_bar_playback_button(context, media_element, components, mouse_position);
|
|
paint_control_bar_timeline(context, media_element, components);
|
|
paint_control_bar_timestamp(context, components);
|
|
paint_control_bar_speaker(context, media_element, components, mouse_position);
|
|
paint_control_bar_volume(context, media_element, components, mouse_position);
|
|
}
|
|
|
|
MediaPaintable::Components MediaPaintable::compute_control_bar_components(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect) const
|
|
{
|
|
auto maximum_control_box_height = context.rounded_device_pixels(40);
|
|
auto component_padding = context.rounded_device_pixels(5);
|
|
|
|
Components components {};
|
|
|
|
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 remaining_rect = components.control_box_rect;
|
|
remaining_rect.shrink(component_padding * 2, 0);
|
|
|
|
auto timeline_rect_height = context.rounded_device_pixels(8);
|
|
if ((timeline_rect_height * 3) <= components.control_box_rect.height()) {
|
|
components.timeline_rect = components.control_box_rect;
|
|
components.timeline_rect.set_height(timeline_rect_height);
|
|
remaining_rect.take_from_top(timeline_rect_height);
|
|
}
|
|
|
|
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);
|
|
|
|
components.speaker_button_size = context.rounded_device_pixels(30);
|
|
if (components.speaker_button_size <= remaining_rect.width()) {
|
|
components.volume_button_size = context.rounded_device_pixels(16);
|
|
|
|
if ((components.speaker_button_size + components.volume_button_size * 3) <= remaining_rect.width()) {
|
|
auto volume_width = min(context.rounded_device_pixels(60), remaining_rect.width() - components.speaker_button_size);
|
|
|
|
components.volume_rect = remaining_rect;
|
|
components.volume_rect.take_from_left(remaining_rect.width() - volume_width);
|
|
remaining_rect.take_from_right(volume_width);
|
|
|
|
components.volume_scrub_rect = components.volume_rect.shrunken(components.volume_button_size, components.volume_rect.height() - components.volume_button_size / 2);
|
|
}
|
|
|
|
components.speaker_button_rect = remaining_rect;
|
|
components.speaker_button_rect.take_from_left(remaining_rect.width() - components.speaker_button_size);
|
|
remaining_rect.take_from_right(components.speaker_button_size + component_padding);
|
|
}
|
|
|
|
auto display_time = human_readable_digital_time(round(media_element.layout_display_time({})));
|
|
auto duration = human_readable_digital_time(isnan(media_element.duration()) ? 0 : round(media_element.duration()));
|
|
components.timestamp = String::formatted("{} / {}", display_time, duration).release_value_but_fixme_should_propagate_errors();
|
|
components.timestamp_font = layout_node().font(context);
|
|
|
|
auto timestamp_size = DevicePixels { static_cast<DevicePixels::Type>(ceilf(components.timestamp_font->width(components.timestamp))) };
|
|
if (timestamp_size <= remaining_rect.width()) {
|
|
components.timestamp_rect = remaining_rect;
|
|
components.timestamp_rect.take_from_right(remaining_rect.width() - timestamp_size);
|
|
remaining_rect.take_from_left(timestamp_size + component_padding);
|
|
}
|
|
|
|
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);
|
|
media_element.cached_layout_boxes({}).speaker_button_rect = context.scale_to_css_rect(components.speaker_button_rect);
|
|
media_element.cached_layout_boxes({}).volume_rect = context.scale_to_css_rect(components.volume_rect);
|
|
media_element.cached_layout_boxes({}).volume_scrub_rect = context.scale_to_css_rect(components.volume_scrub_rect);
|
|
|
|
return components;
|
|
}
|
|
|
|
void MediaPaintable::paint_control_bar_playback_button(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional<DevicePixelPoint> 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 = rect_is_hovered(media_element, components.playback_button_rect, mouse_position);
|
|
auto playback_button_color = control_button_color(playback_button_is_hovered);
|
|
|
|
if (media_element.paused()) {
|
|
Array<Gfx::IntPoint, 3> play_button_coordinates { {
|
|
{ 0, 0 },
|
|
{ static_cast<int>(playback_button_size), static_cast<int>(playback_button_size) / 2 },
|
|
{ 0, static_cast<int>(playback_button_size) },
|
|
} };
|
|
|
|
fill_triangle(context.display_list_recorder(), playback_button_location.to_type<int>(), play_button_coordinates, playback_button_color);
|
|
} else {
|
|
DevicePixelRect pause_button_left_rect {
|
|
playback_button_location,
|
|
{ playback_button_size / 3, playback_button_size }
|
|
};
|
|
DevicePixelRect pause_button_right_rect {
|
|
playback_button_location.translated(playback_button_size * 2 / 3, 0),
|
|
{ playback_button_size / 3, playback_button_size }
|
|
};
|
|
|
|
context.display_list_recorder().fill_rect(pause_button_left_rect.to_type<int>(), playback_button_color);
|
|
context.display_list_recorder().fill_rect(pause_button_right_rect.to_type<int>(), playback_button_color);
|
|
}
|
|
}
|
|
|
|
void MediaPaintable::paint_control_bar_timeline(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components)
|
|
{
|
|
if (components.timeline_rect.is_empty())
|
|
return;
|
|
|
|
auto playback_percentage = isnan(media_element.duration()) ? 0.0 : media_element.layout_display_time({}) / media_element.duration();
|
|
auto playback_position = static_cast<double>(static_cast<int>(components.timeline_rect.width())) * playback_percentage;
|
|
auto timeline_button_offset_x = static_cast<DevicePixels>(round(playback_position));
|
|
|
|
auto timeline_past_rect = components.timeline_rect;
|
|
timeline_past_rect.set_width(timeline_button_offset_x);
|
|
context.display_list_recorder().fill_rect(timeline_past_rect.to_type<int>(), CONTROL_HIGHLIGHT_COLOR);
|
|
|
|
auto timeline_future_rect = components.timeline_rect;
|
|
timeline_future_rect.take_from_left(timeline_button_offset_x);
|
|
context.display_list_recorder().fill_rect(timeline_future_rect.to_type<int>(), Color::Black);
|
|
}
|
|
|
|
void MediaPaintable::paint_control_bar_timestamp(PaintContext& context, Components const& components)
|
|
{
|
|
if (components.timestamp_rect.is_empty())
|
|
return;
|
|
|
|
context.display_list_recorder().draw_text(components.timestamp_rect.to_type<int>(), components.timestamp, *components.timestamp_font, Gfx::TextAlignment::CenterLeft, Color::White);
|
|
}
|
|
|
|
void MediaPaintable::paint_control_bar_speaker(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional<DevicePixelPoint> const& mouse_position)
|
|
{
|
|
if (components.speaker_button_rect.is_empty())
|
|
return;
|
|
|
|
auto speaker_button_width = context.rounded_device_pixels(20);
|
|
auto speaker_button_height = context.rounded_device_pixels(15);
|
|
|
|
auto speaker_button_offset_x = (components.speaker_button_rect.width() - speaker_button_width) / 2;
|
|
auto speaker_button_offset_y = (components.speaker_button_rect.height() - speaker_button_height) / 2;
|
|
auto speaker_button_location = components.speaker_button_rect.top_left().translated(speaker_button_offset_x, speaker_button_offset_y);
|
|
|
|
auto device_point = [&](double x, double y) {
|
|
auto position = context.rounded_device_point({ x, y }) + speaker_button_location;
|
|
return position.to_type<DevicePixels::Type>().to_type<float>();
|
|
};
|
|
|
|
auto speaker_button_is_hovered = rect_is_hovered(media_element, components.speaker_button_rect, mouse_position);
|
|
auto speaker_button_color = control_button_color(speaker_button_is_hovered);
|
|
|
|
Gfx::Path path;
|
|
|
|
path.move_to(device_point(0, 4));
|
|
path.line_to(device_point(5, 4));
|
|
path.line_to(device_point(11, 0));
|
|
path.line_to(device_point(11, 15));
|
|
path.line_to(device_point(5, 11));
|
|
path.line_to(device_point(0, 11));
|
|
path.line_to(device_point(0, 4));
|
|
path.close();
|
|
context.display_list_recorder().fill_path({ .path = path, .color = speaker_button_color, .winding_rule = Gfx::WindingRule::EvenOdd });
|
|
|
|
path.clear();
|
|
path.move_to(device_point(13, 3));
|
|
path.quadratic_bezier_curve_to(device_point(16, 7.5), device_point(13, 12));
|
|
path.move_to(device_point(14, 0));
|
|
path.quadratic_bezier_curve_to(device_point(20, 7.5), device_point(14, 15));
|
|
context.display_list_recorder().stroke_path({
|
|
.cap_style = Gfx::Path::CapStyle::Round,
|
|
.join_style = Gfx::Path::JoinStyle::Round,
|
|
.miter_limit = 4,
|
|
.dash_array = {},
|
|
.dash_offset = 0,
|
|
.path = path,
|
|
.color = speaker_button_color,
|
|
.thickness = 1,
|
|
});
|
|
|
|
if (media_element.muted()) {
|
|
context.display_list_recorder().draw_line(device_point(0, 0).to_type<int>(), device_point(20, 15).to_type<int>(), Color::Red, 2);
|
|
context.display_list_recorder().draw_line(device_point(0, 15).to_type<int>(), device_point(20, 0).to_type<int>(), Color::Red, 2);
|
|
}
|
|
}
|
|
|
|
void MediaPaintable::paint_control_bar_volume(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional<DevicePixelPoint> const& mouse_position)
|
|
{
|
|
if (components.volume_rect.is_empty())
|
|
return;
|
|
|
|
auto volume_position = static_cast<double>(static_cast<int>(components.volume_scrub_rect.width())) * media_element.volume();
|
|
auto volume_button_offset_x = static_cast<DevicePixels>(round(volume_position));
|
|
|
|
auto volume_lower_rect = components.volume_scrub_rect;
|
|
volume_lower_rect.set_width(volume_button_offset_x);
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(volume_lower_rect.to_type<int>(), CONTROL_HIGHLIGHT_COLOR, 4);
|
|
|
|
auto volume_higher_rect = components.volume_scrub_rect;
|
|
volume_higher_rect.take_from_left(volume_button_offset_x);
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(volume_higher_rect.to_type<int>(), Color::Black, 4);
|
|
|
|
auto volume_button_rect = components.volume_scrub_rect;
|
|
volume_button_rect.shrink(components.volume_scrub_rect.width() - components.volume_button_size, components.volume_scrub_rect.height() - components.volume_button_size);
|
|
volume_button_rect.set_x(components.volume_scrub_rect.x() + volume_button_offset_x - components.volume_button_size / 2);
|
|
|
|
auto volume_is_hovered = rect_is_hovered(media_element, components.volume_rect, mouse_position, HTML::HTMLMediaElement::MediaComponent::Volume);
|
|
auto volume_color = control_button_color(volume_is_hovered);
|
|
context.display_list_recorder().fill_ellipse(volume_button_rect.to_type<int>(), volume_color);
|
|
}
|
|
|
|
MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mousedown(Badge<EventHandler>, CSSPixelPoint position, unsigned button, unsigned)
|
|
{
|
|
if (button != UIEvents::MouseButton::Primary)
|
|
return DispatchEventOfSameName::Yes;
|
|
|
|
auto& media_element = *as<HTML::HTMLMediaElement>(layout_node_with_style_and_box_metrics().dom_node());
|
|
auto const& cached_layout_boxes = media_element.cached_layout_boxes({});
|
|
|
|
auto position_adjusted_by_scroll_offset = position;
|
|
position_adjusted_by_scroll_offset.translate_by(-cumulative_offset_of_enclosing_scroll_frame());
|
|
|
|
if (cached_layout_boxes.timeline_rect.has_value() && cached_layout_boxes.timeline_rect->contains(position_adjusted_by_scroll_offset)) {
|
|
media_element.set_layout_mouse_tracking_component({}, HTML::HTMLMediaElement::MediaComponent::Timeline);
|
|
set_current_time(media_element, *cached_layout_boxes.timeline_rect, position_adjusted_by_scroll_offset, Temporary::Yes);
|
|
} else if (cached_layout_boxes.volume_rect.has_value() && cached_layout_boxes.volume_rect->contains(position_adjusted_by_scroll_offset)) {
|
|
media_element.set_layout_mouse_tracking_component({}, HTML::HTMLMediaElement::MediaComponent::Volume);
|
|
set_volume(media_element, *cached_layout_boxes.volume_scrub_rect, position_adjusted_by_scroll_offset);
|
|
}
|
|
|
|
if (media_element.layout_mouse_tracking_component({}).has_value())
|
|
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(this);
|
|
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badge<EventHandler>, CSSPixelPoint position, unsigned button, unsigned)
|
|
{
|
|
auto& media_element = *as<HTML::HTMLMediaElement>(layout_node_with_style_and_box_metrics().dom_node());
|
|
auto const& cached_layout_boxes = media_element.cached_layout_boxes({});
|
|
|
|
auto position_adjusted_by_scroll_offset = position;
|
|
position_adjusted_by_scroll_offset.translate_by(-cumulative_offset_of_enclosing_scroll_frame());
|
|
|
|
if (auto const& mouse_tracking_component = media_element.layout_mouse_tracking_component({}); mouse_tracking_component.has_value()) {
|
|
switch (*mouse_tracking_component) {
|
|
case HTML::HTMLMediaElement::MediaComponent::Timeline:
|
|
set_current_time(media_element, *cached_layout_boxes.timeline_rect, position_adjusted_by_scroll_offset, Temporary::No);
|
|
media_element.set_layout_display_time({}, {});
|
|
break;
|
|
|
|
case HTML::HTMLMediaElement::MediaComponent::Volume:
|
|
document().page().client().page_did_stop_tooltip_override();
|
|
break;
|
|
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(nullptr);
|
|
media_element.set_layout_mouse_tracking_component({}, {});
|
|
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
if (button != UIEvents::MouseButton::Primary)
|
|
return DispatchEventOfSameName::Yes;
|
|
|
|
if (cached_layout_boxes.control_box_rect.has_value() && cached_layout_boxes.control_box_rect->contains(position_adjusted_by_scroll_offset)) {
|
|
if (cached_layout_boxes.playback_button_rect.has_value() && cached_layout_boxes.playback_button_rect->contains(position_adjusted_by_scroll_offset)) {
|
|
media_element.toggle_playback().release_value_but_fixme_should_propagate_errors();
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
if (cached_layout_boxes.speaker_button_rect.has_value() && cached_layout_boxes.speaker_button_rect->contains(position_adjusted_by_scroll_offset)) {
|
|
media_element.set_muted(!media_element.muted());
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
if (cached_layout_boxes.timeline_rect.has_value() && cached_layout_boxes.timeline_rect->contains(position_adjusted_by_scroll_offset))
|
|
return DispatchEventOfSameName::No;
|
|
}
|
|
|
|
media_element.toggle_playback().release_value_but_fixme_should_propagate_errors();
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mousemove(Badge<EventHandler>, CSSPixelPoint position, unsigned, unsigned)
|
|
{
|
|
auto& media_element = *as<HTML::HTMLMediaElement>(layout_node_with_style_and_box_metrics().dom_node());
|
|
auto const& cached_layout_boxes = media_element.cached_layout_boxes({});
|
|
|
|
auto position_adjusted_by_scroll_offset = position;
|
|
auto scroll_offset = cumulative_offset_of_enclosing_scroll_frame();
|
|
position_adjusted_by_scroll_offset.translate_by(-scroll_offset);
|
|
|
|
if (auto const& mouse_tracking_component = media_element.layout_mouse_tracking_component({}); mouse_tracking_component.has_value()) {
|
|
switch (*mouse_tracking_component) {
|
|
case HTML::HTMLMediaElement::MediaComponent::Timeline:
|
|
if (cached_layout_boxes.timeline_rect.has_value())
|
|
set_current_time(media_element, *cached_layout_boxes.timeline_rect, position_adjusted_by_scroll_offset, Temporary::Yes);
|
|
break;
|
|
|
|
case HTML::HTMLMediaElement::MediaComponent::Volume:
|
|
if (cached_layout_boxes.volume_rect.has_value()) {
|
|
set_volume(media_element, *cached_layout_boxes.volume_scrub_rect, position_adjusted_by_scroll_offset);
|
|
|
|
auto volume = static_cast<u8>(media_element.volume() * 100.0);
|
|
document().page().client().page_did_request_tooltip_override({ position_adjusted_by_scroll_offset.x(), cached_layout_boxes.volume_scrub_rect->y() + scroll_offset.y() }, ByteString::formatted("{}%", volume));
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
auto previous_hovered_component = media_element.layout_hovered_component({});
|
|
|
|
if (cached_layout_boxes.playback_button_rect.has_value() && cached_layout_boxes.playback_button_rect->contains(position_adjusted_by_scroll_offset))
|
|
media_element.set_layout_hovered_component({}, HTML::HTMLMediaElement::MediaComponent::PlaybackButton);
|
|
else if (cached_layout_boxes.speaker_button_rect.has_value() && cached_layout_boxes.speaker_button_rect->contains(position_adjusted_by_scroll_offset))
|
|
media_element.set_layout_hovered_component({}, HTML::HTMLMediaElement::MediaComponent::SpeakerButton);
|
|
else if (cached_layout_boxes.volume_rect.has_value() && cached_layout_boxes.volume_rect->contains(position_adjusted_by_scroll_offset))
|
|
media_element.set_layout_hovered_component({}, HTML::HTMLMediaElement::MediaComponent::Volume);
|
|
else
|
|
media_element.set_layout_hovered_component({}, {});
|
|
|
|
if (previous_hovered_component != media_element.layout_hovered_component({}))
|
|
set_needs_display();
|
|
|
|
if (absolute_rect().contains(position_adjusted_by_scroll_offset)) {
|
|
media_element.set_layout_mouse_position({}, position_adjusted_by_scroll_offset);
|
|
return DispatchEventOfSameName::Yes;
|
|
}
|
|
|
|
media_element.set_layout_mouse_position({}, {});
|
|
return DispatchEventOfSameName::No;
|
|
}
|
|
|
|
void MediaPaintable::set_current_time(HTML::HTMLMediaElement& media_element, CSSPixelRect timeline_rect, CSSPixelPoint mouse_position, Temporary temporarily)
|
|
{
|
|
auto x_offset = mouse_position.x() - timeline_rect.x();
|
|
x_offset = max(x_offset, 0);
|
|
x_offset = min(x_offset, timeline_rect.width());
|
|
|
|
auto x_percentage = static_cast<double>(x_offset) / static_cast<double>(timeline_rect.width());
|
|
auto position = static_cast<double>(x_percentage) * media_element.duration();
|
|
|
|
switch (temporarily) {
|
|
case Temporary::Yes:
|
|
media_element.set_layout_display_time({}, position);
|
|
break;
|
|
case Temporary::No:
|
|
media_element.set_current_time(position);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MediaPaintable::set_volume(HTML::HTMLMediaElement& media_element, CSSPixelRect volume_rect, CSSPixelPoint mouse_position)
|
|
{
|
|
auto x_offset = mouse_position.x() - volume_rect.x();
|
|
x_offset = max(x_offset, 0);
|
|
x_offset = min(x_offset, volume_rect.width());
|
|
|
|
auto volume = static_cast<double>(x_offset) / static_cast<double>(volume_rect.width());
|
|
media_element.set_volume(volume).release_value_but_fixme_should_propagate_errors();
|
|
}
|
|
|
|
bool MediaPaintable::rect_is_hovered(HTML::HTMLMediaElement const& media_element, Optional<DevicePixelRect> const& rect, Optional<DevicePixelPoint> const& mouse_position, Optional<HTML::HTMLMediaElement::MediaComponent> const& allowed_mouse_tracking_component)
|
|
{
|
|
if (auto const& mouse_tracking_component = media_element.layout_mouse_tracking_component({}); mouse_tracking_component.has_value())
|
|
return mouse_tracking_component == allowed_mouse_tracking_component;
|
|
|
|
if (!rect.has_value() || !mouse_position.has_value())
|
|
return false;
|
|
|
|
return rect->contains(*mouse_position);
|
|
}
|
|
|
|
}
|