Allow hovered and clicked mouse regions to be tracked in the presenter
This commit is contained in:
parent
0866f0ed55
commit
3a59d2a331
3 changed files with 133 additions and 8 deletions
|
@ -16,7 +16,7 @@ pub mod fonts;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
mod presenter;
|
mod presenter;
|
||||||
mod scene;
|
mod scene;
|
||||||
pub use scene::{Border, CursorRegion, Quad, Scene};
|
pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene};
|
||||||
pub mod text_layout;
|
pub mod text_layout;
|
||||||
pub use text_layout::TextLayoutCache;
|
pub use text_layout::TextLayoutCache;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
|
@ -8,8 +8,9 @@ use crate::{
|
||||||
scene::CursorRegion,
|
scene::CursorRegion,
|
||||||
text_layout::TextLayoutCache,
|
text_layout::TextLayoutCache,
|
||||||
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox,
|
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox,
|
||||||
ElementStateContext, Entity, FontSystem, ModelHandle, ReadModel, ReadView, Scene,
|
ElementStateContext, Entity, FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel,
|
||||||
UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
|
ReadView, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle,
|
||||||
|
WeakViewHandle,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -24,10 +25,13 @@ pub struct Presenter {
|
||||||
pub(crate) rendered_views: HashMap<usize, ElementBox>,
|
pub(crate) rendered_views: HashMap<usize, ElementBox>,
|
||||||
parents: HashMap<usize, usize>,
|
parents: HashMap<usize, usize>,
|
||||||
cursor_regions: Vec<CursorRegion>,
|
cursor_regions: Vec<CursorRegion>,
|
||||||
|
mouse_regions: Vec<MouseRegion>,
|
||||||
font_cache: Arc<FontCache>,
|
font_cache: Arc<FontCache>,
|
||||||
text_layout_cache: TextLayoutCache,
|
text_layout_cache: TextLayoutCache,
|
||||||
asset_cache: Arc<AssetCache>,
|
asset_cache: Arc<AssetCache>,
|
||||||
last_mouse_moved_event: Option<Event>,
|
last_mouse_moved_event: Option<Event>,
|
||||||
|
hovered_region_id: Option<MouseRegionId>,
|
||||||
|
clicked_region: Option<MouseRegion>,
|
||||||
titlebar_height: f32,
|
titlebar_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,10 +49,13 @@ impl Presenter {
|
||||||
rendered_views: cx.render_views(window_id, titlebar_height),
|
rendered_views: cx.render_views(window_id, titlebar_height),
|
||||||
parents: HashMap::new(),
|
parents: HashMap::new(),
|
||||||
cursor_regions: Default::default(),
|
cursor_regions: Default::default(),
|
||||||
|
mouse_regions: Default::default(),
|
||||||
font_cache,
|
font_cache,
|
||||||
text_layout_cache,
|
text_layout_cache,
|
||||||
asset_cache,
|
asset_cache,
|
||||||
last_mouse_moved_event: None,
|
last_mouse_moved_event: None,
|
||||||
|
hovered_region_id: None,
|
||||||
|
clicked_region: None,
|
||||||
titlebar_height,
|
titlebar_height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +129,7 @@ impl Presenter {
|
||||||
);
|
);
|
||||||
self.text_layout_cache.finish_frame();
|
self.text_layout_cache.finish_frame();
|
||||||
self.cursor_regions = scene.cursor_regions();
|
self.cursor_regions = scene.cursor_regions();
|
||||||
|
self.mouse_regions = scene.mouse_regions();
|
||||||
|
|
||||||
if cx.window_is_active(self.window_id) {
|
if cx.window_is_active(self.window_id) {
|
||||||
if let Some(event) = self.last_mouse_moved_event.clone() {
|
if let Some(event) = self.last_mouse_moved_event.clone() {
|
||||||
|
@ -176,7 +184,30 @@ impl Presenter {
|
||||||
|
|
||||||
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
|
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
|
||||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||||
|
let mut unhovered_region = None;
|
||||||
|
let mut hovered_region = None;
|
||||||
|
let mut clicked_region = None;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
Event::LeftMouseDown { position, .. } => {
|
||||||
|
for region in self.mouse_regions.iter().rev() {
|
||||||
|
if region.bounds.contains_point(position) {
|
||||||
|
self.clicked_region = Some(region.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::LeftMouseUp {
|
||||||
|
position,
|
||||||
|
click_count,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(region) = self.clicked_region.take() {
|
||||||
|
if region.bounds.contains_point(position) {
|
||||||
|
clicked_region = Some((region, position, click_count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Event::MouseMoved {
|
Event::MouseMoved {
|
||||||
position,
|
position,
|
||||||
left_mouse_down,
|
left_mouse_down,
|
||||||
|
@ -192,6 +223,18 @@ impl Presenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.platform().set_cursor_style(style_to_assign);
|
cx.platform().set_cursor_style(style_to_assign);
|
||||||
|
|
||||||
|
for region in self.mouse_regions.iter().rev() {
|
||||||
|
if region.bounds.contains_point(position) {
|
||||||
|
if hovered_region.is_none() {
|
||||||
|
hovered_region = Some(region.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.hovered_region_id == Some(region.id()) {
|
||||||
|
unhovered_region = Some(region.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::LeftMouseDragged { position } => {
|
Event::LeftMouseDragged { position } => {
|
||||||
|
@ -203,7 +246,33 @@ impl Presenter {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.hovered_region_id = hovered_region.as_ref().map(MouseRegion::id);
|
||||||
|
|
||||||
let mut event_cx = self.build_event_context(cx);
|
let mut event_cx = self.build_event_context(cx);
|
||||||
|
if let Some(unhovered_region) = unhovered_region {
|
||||||
|
if let Some(hover_callback) = unhovered_region.hover {
|
||||||
|
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
|
||||||
|
hover_callback(false, event_cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(hovered_region) = hovered_region {
|
||||||
|
if let Some(hover_callback) = hovered_region.hover {
|
||||||
|
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
|
||||||
|
hover_callback(true, event_cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((clicked_region, position, click_count)) = clicked_region {
|
||||||
|
if let Some(click_callback) = clicked_region.click {
|
||||||
|
event_cx.with_current_view(clicked_region.view_id, |event_cx| {
|
||||||
|
click_callback(position, click_count, event_cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event_cx.dispatch_event(root_view_id, &event);
|
event_cx.dispatch_event(root_view_id, &event);
|
||||||
|
|
||||||
let invalidated_views = event_cx.invalidated_views;
|
let invalidated_views = event_cx.invalidated_views;
|
||||||
|
@ -379,9 +448,8 @@ pub struct EventContext<'a> {
|
||||||
impl<'a> EventContext<'a> {
|
impl<'a> EventContext<'a> {
|
||||||
fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
|
fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
|
||||||
if let Some(mut element) = self.rendered_views.remove(&view_id) {
|
if let Some(mut element) = self.rendered_views.remove(&view_id) {
|
||||||
self.view_stack.push(view_id);
|
let result =
|
||||||
let result = element.dispatch_event(event, self);
|
self.with_current_view(view_id, |this| element.dispatch_event(event, this));
|
||||||
self.view_stack.pop();
|
|
||||||
self.rendered_views.insert(view_id, element);
|
self.rendered_views.insert(view_id, element);
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
|
@ -389,6 +457,16 @@ impl<'a> EventContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> T,
|
||||||
|
{
|
||||||
|
self.view_stack.push(view_id);
|
||||||
|
let result = f(self);
|
||||||
|
self.view_stack.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
|
||||||
self.dispatched_actions.push(DispatchDirective {
|
self.dispatched_actions.push(DispatchDirective {
|
||||||
path: self.view_stack.clone(),
|
path: self.view_stack.clone(),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{any::TypeId, borrow::Cow, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::Color,
|
color::Color,
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::ToJson,
|
json::ToJson,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
ImageData,
|
EventContext, ImageData,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
|
@ -34,6 +34,7 @@ pub struct Layer {
|
||||||
icons: Vec<Icon>,
|
icons: Vec<Icon>,
|
||||||
paths: Vec<Path>,
|
paths: Vec<Path>,
|
||||||
cursor_regions: Vec<CursorRegion>,
|
cursor_regions: Vec<CursorRegion>,
|
||||||
|
mouse_regions: Vec<MouseRegion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
@ -42,6 +43,23 @@ pub struct CursorRegion {
|
||||||
pub style: CursorStyle,
|
pub style: CursorStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MouseRegion {
|
||||||
|
pub view_id: usize,
|
||||||
|
pub tag: TypeId,
|
||||||
|
pub region_id: usize,
|
||||||
|
pub bounds: RectF,
|
||||||
|
pub hover: Option<Rc<dyn Fn(bool, &mut EventContext)>>,
|
||||||
|
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct MouseRegionId {
|
||||||
|
pub view_id: usize,
|
||||||
|
pub tag: TypeId,
|
||||||
|
pub region_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Quad {
|
pub struct Quad {
|
||||||
pub bounds: RectF,
|
pub bounds: RectF,
|
||||||
|
@ -188,6 +206,13 @@ impl Scene {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mouse_regions(&self) -> Vec<MouseRegion> {
|
||||||
|
self.layers()
|
||||||
|
.flat_map(|layer| &layer.mouse_regions)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
|
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
|
||||||
self.active_stacking_context_stack
|
self.active_stacking_context_stack
|
||||||
.push(self.stacking_contexts.len());
|
.push(self.stacking_contexts.len());
|
||||||
|
@ -305,6 +330,7 @@ impl Layer {
|
||||||
icons: Default::default(),
|
icons: Default::default(),
|
||||||
paths: Default::default(),
|
paths: Default::default(),
|
||||||
cursor_regions: Default::default(),
|
cursor_regions: Default::default(),
|
||||||
|
mouse_regions: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,6 +359,17 @@ impl Layer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_mouse_region(&mut self, region: MouseRegion) {
|
||||||
|
if let Some(bounds) = region
|
||||||
|
.bounds
|
||||||
|
.intersection(self.clip_bounds.unwrap_or(region.bounds))
|
||||||
|
{
|
||||||
|
if can_draw(bounds) {
|
||||||
|
self.mouse_regions.push(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn push_underline(&mut self, underline: Underline) {
|
fn push_underline(&mut self, underline: Underline) {
|
||||||
if underline.width > 0. {
|
if underline.width > 0. {
|
||||||
self.underlines.push(underline);
|
self.underlines.push(underline);
|
||||||
|
@ -493,6 +530,16 @@ impl ToJson for Border {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MouseRegion {
|
||||||
|
pub fn id(&self) -> MouseRegionId {
|
||||||
|
MouseRegionId {
|
||||||
|
view_id: self.view_id,
|
||||||
|
tag: self.tag,
|
||||||
|
region_id: self.region_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn can_draw(bounds: RectF) -> bool {
|
fn can_draw(bounds: RectF) -> bool {
|
||||||
let size = bounds.size();
|
let size = bounds.size();
|
||||||
size.x() > 0. && size.y() > 0.
|
size.x() > 0. && size.y() > 0.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue