core: Render yellow highlight on keyboard focus
This patch implements rendering of the yellow rectangle around a focused element after pressing Tab. Focus tracker which is responsible for keeping track of the current focus is now also responsible for keeping track of the highlight and rendering thereof.
This commit is contained in:
parent
068363a87c
commit
95983bf4f3
|
@ -1926,6 +1926,11 @@ pub trait TDisplayObject<'gc>:
|
|||
None
|
||||
}
|
||||
|
||||
/// Whether this object may be highlighted by tab ordering.
|
||||
fn is_highlight_enabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether this display object has been created by ActionScript 3.
|
||||
/// When this flag is set, changes from SWF `RemoveObject` tags are
|
||||
/// ignored.
|
||||
|
|
|
@ -430,6 +430,11 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
|
|||
self.0.tab_index.get().map(|i| i as i64)
|
||||
}
|
||||
|
||||
fn is_highlight_enabled(&self) -> bool {
|
||||
// TODO focusrect support
|
||||
true
|
||||
}
|
||||
|
||||
fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) {
|
||||
let had_focus = self.0.has_focus.get();
|
||||
if had_focus {
|
||||
|
|
|
@ -3041,6 +3041,11 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
|||
fn tab_index(&self) -> Option<i64> {
|
||||
self.0.read().tab_index.map(|i| i as i64)
|
||||
}
|
||||
|
||||
fn is_highlight_enabled(&self) -> bool {
|
||||
// TODO focusrect support
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TDisplayObjectContainer<'gc> for MovieClip<'gc> {
|
||||
|
|
|
@ -847,6 +847,8 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
|
|||
|
||||
render_base((*self).into(), context);
|
||||
|
||||
self.focus_tracker().render_highlight(context);
|
||||
|
||||
if self.should_letterbox() {
|
||||
self.draw_letterbox(context);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,58 @@
|
|||
use crate::avm1::Avm1;
|
||||
use crate::avm1::Value;
|
||||
use crate::context::UpdateContext;
|
||||
use crate::context::{RenderContext, UpdateContext};
|
||||
pub use crate::display_object::{
|
||||
DisplayObject, TDisplayObject, TDisplayObjectContainer, TextSelection,
|
||||
};
|
||||
use crate::drawing::Drawing;
|
||||
use either::Either;
|
||||
use gc_arena::lock::GcLock;
|
||||
use gc_arena::{Collect, Mutation};
|
||||
use swf::Twips;
|
||||
use gc_arena::barrier::unlock;
|
||||
use gc_arena::lock::Lock;
|
||||
use gc_arena::{Collect, Gc, Mutation};
|
||||
use ruffle_render::shape_utils::DrawCommand;
|
||||
use std::cell::RefCell;
|
||||
use swf::{Color, LineJoinStyle, Point, Twips};
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct FocusTrackerData<'gc> {
|
||||
focus: Lock<Option<DisplayObject<'gc>>>,
|
||||
highlight: RefCell<Highlight>,
|
||||
}
|
||||
|
||||
enum Highlight {
|
||||
Inactive,
|
||||
Active(Drawing),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct FocusTracker<'gc>(GcLock<'gc, Option<DisplayObject<'gc>>>);
|
||||
pub struct FocusTracker<'gc>(Gc<'gc, FocusTrackerData<'gc>>);
|
||||
|
||||
impl<'gc> FocusTracker<'gc> {
|
||||
const HIGHLIGHT_WIDTH: Twips = Twips::from_pixels_i32(3);
|
||||
const HIGHLIGHT_COLOR: Color = Color::YELLOW;
|
||||
|
||||
// Although at 3px width Round and Miter are similar
|
||||
// to each other, it seems that FP uses Round.
|
||||
const HIGHLIGHT_LINE_JOIN_STYLE: LineJoinStyle = LineJoinStyle::Round;
|
||||
|
||||
pub fn new(mc: &Mutation<'gc>) -> Self {
|
||||
Self(GcLock::new(mc, None.into()))
|
||||
Self(Gc::new(
|
||||
mc,
|
||||
FocusTrackerData {
|
||||
focus: Lock::new(None),
|
||||
highlight: RefCell::new(Highlight::Inactive),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn reset_highlight(&self) {
|
||||
self.0.highlight.replace(Highlight::Inactive);
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<DisplayObject<'gc>> {
|
||||
self.0.get()
|
||||
self.0.focus.get()
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
|
@ -27,11 +60,15 @@ impl<'gc> FocusTracker<'gc> {
|
|||
focused_element: Option<DisplayObject<'gc>>,
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
) {
|
||||
let old = self.0.get();
|
||||
let old = self.0.focus.get();
|
||||
|
||||
// Check if the focused element changed.
|
||||
if old.map(|o| o.as_ptr()) != focused_element.map(|o| o.as_ptr()) {
|
||||
self.0.set(context.gc(), focused_element);
|
||||
let focus = unlock!(Gc::write(context.gc(), self.0), FocusTrackerData, focus);
|
||||
focus.set(focused_element);
|
||||
|
||||
// The highlight always follows the focus.
|
||||
self.update_highlight();
|
||||
|
||||
if let Some(old) = old {
|
||||
old.on_focus_changed(context, false, focused_element);
|
||||
|
@ -89,7 +126,7 @@ impl<'gc> FocusTracker<'gc> {
|
|||
.peekable();
|
||||
let first = tab_order.peek().copied();
|
||||
|
||||
let next = if let Some(current_focus) = self.0.get() {
|
||||
let next = if let Some(current_focus) = self.get() {
|
||||
// Find the next object which should take the focus.
|
||||
tab_order
|
||||
.skip_while(|o| o.as_ptr() != current_focus.as_ptr())
|
||||
|
@ -102,9 +139,46 @@ impl<'gc> FocusTracker<'gc> {
|
|||
|
||||
if next.is_some() {
|
||||
self.set(next.copied(), context);
|
||||
self.update_highlight();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_highlight(&self) {
|
||||
self.0.highlight.replace(self.redraw_highlight());
|
||||
}
|
||||
|
||||
fn redraw_highlight(&self) -> Highlight {
|
||||
let Some(focus) = self.get() else {
|
||||
return Highlight::Inactive;
|
||||
};
|
||||
|
||||
if !focus.is_highlight_enabled() {
|
||||
return Highlight::Inactive;
|
||||
}
|
||||
|
||||
let bounds = focus.world_bounds().grow(-Self::HIGHLIGHT_WIDTH / 2);
|
||||
let mut drawing = Drawing::new();
|
||||
drawing.set_line_style(Some(
|
||||
swf::LineStyle::new()
|
||||
.with_width(Self::HIGHLIGHT_WIDTH)
|
||||
.with_color(Self::HIGHLIGHT_COLOR)
|
||||
.with_join_style(Self::HIGHLIGHT_LINE_JOIN_STYLE),
|
||||
));
|
||||
drawing.draw_command(DrawCommand::MoveTo(Point::new(bounds.x_min, bounds.y_min)));
|
||||
drawing.draw_command(DrawCommand::LineTo(Point::new(bounds.x_min, bounds.y_max)));
|
||||
drawing.draw_command(DrawCommand::LineTo(Point::new(bounds.x_max, bounds.y_max)));
|
||||
drawing.draw_command(DrawCommand::LineTo(Point::new(bounds.x_max, bounds.y_min)));
|
||||
drawing.draw_command(DrawCommand::LineTo(Point::new(bounds.x_min, bounds.y_min)));
|
||||
|
||||
Highlight::Active(drawing)
|
||||
}
|
||||
|
||||
pub fn render_highlight(&self, context: &mut RenderContext<'_, 'gc>) {
|
||||
if let Highlight::Active(ref highlight) = *self.0.highlight.borrow() {
|
||||
highlight.render(context);
|
||||
};
|
||||
}
|
||||
|
||||
fn order_custom(tab_order: &mut Vec<DisplayObject>) {
|
||||
// Custom ordering disables automatic ordering and
|
||||
// ignores all objects without tabIndex.
|
||||
|
|
|
@ -1187,6 +1187,17 @@ impl Player {
|
|||
tracker.cycle(context, reversed);
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(
|
||||
event,
|
||||
PlayerEvent::MouseDown { .. }
|
||||
| PlayerEvent::MouseUp { .. }
|
||||
| PlayerEvent::MouseMove { .. }
|
||||
) {
|
||||
self.mutate_with_update_context(|context| {
|
||||
context.focus_tracker.reset_highlight();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Update dragged object, if any.
|
||||
|
|
|
@ -23,6 +23,9 @@ impl Color {
|
|||
pub const RED: Self = Self::from_rgb(0xFF0000, 255);
|
||||
pub const GREEN: Self = Self::from_rgb(0x00FF00, 255);
|
||||
pub const BLUE: Self = Self::from_rgb(0x0000FF, 255);
|
||||
pub const YELLOW: Self = Self::from_rgb(0xFFFF00, 255);
|
||||
pub const CYAN: Self = Self::from_rgb(0x00FFFF, 255);
|
||||
pub const MAGENTA: Self = Self::from_rgb(0xFF00FF, 255);
|
||||
|
||||
/// Creates a `Color` from a 32-bit `rgb` value and an `alpha` value.
|
||||
///
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 4.9 KiB |
Loading…
Reference in New Issue