From 0ee7306720c0ad78bc67e62079b63fa112810a41 Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 20:36:24 +0200 Subject: [PATCH 01/11] - implemented z-stack - added MouseEvent::obstructed - changed WidgetPod::event method to handle obstructed flag --- druid-shell/examples/shello.rs | 9 +--- druid/examples/z_stack.rs | 60 ++++++++++++++++++++++ druid/src/core.rs | 8 +-- druid/src/event.rs | 27 ++++++++++ druid/src/mouse.rs | 7 +++ druid/src/tests/mod.rs | 2 + druid/src/widget/mod.rs | 2 + druid/src/widget/z_stack.rs | 92 ++++++++++++++++++++++++++++++++++ 8 files changed, 196 insertions(+), 11 deletions(-) create mode 100644 druid/examples/z_stack.rs create mode 100644 druid/src/widget/z_stack.rs diff --git a/druid-shell/examples/shello.rs b/druid-shell/examples/shello.rs index b0e09b6bb7..804ccdf684 100644 --- a/druid-shell/examples/shello.rs +++ b/druid-shell/examples/shello.rs @@ -22,9 +22,6 @@ use druid_shell::{ Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; -const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22); -const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); - #[derive(Default)] struct HelloState { size: Size, @@ -38,10 +35,8 @@ impl WinHandler for HelloState { fn prepare_paint(&mut self) {} - fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { - let rect = self.size.to_rect(); - piet.fill(rect, &BG_COLOR); - piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0); + fn paint(&mut self, _: &Region) { + println!("paint"); } fn command(&mut self, id: u32) { diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs new file mode 100644 index 0000000000..97d2c92bd5 --- /dev/null +++ b/druid/examples/z_stack.rs @@ -0,0 +1,60 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This is a very small example of how to setup a druid application. +//! It does the almost bare minimum while still being useful. + +// On Windows platform, don't show a console when opening the app. +#![windows_subsystem = "windows"] + +use druid::widget::prelude::*; +use druid::widget::{Button, Flex, Label, TextBox, ZStack}; +use druid::{AppLauncher, Data, Lens, UnitPoint, WidgetExt, WindowDesc}; + +const VERTICAL_WIDGET_SPACING: f64 = 20.0; +const TEXT_BOX_WIDTH: f64 = 200.0; + +#[derive(Clone, Data, Lens)] +struct State { + counter: usize, +} + +pub fn main() { + // describe the main window + let main_window = WindowDesc::new(build_root_widget()) + .title("Hello World!") + .window_size((400.0, 400.0)); + + // create the initial app state + let initial_state: State = State { + counter: 0, + }; + + // start the application. Here we pass in the application state. + AppLauncher::with_window(main_window) + .log_to_console() + .launch(initial_state) + .expect("Failed to launch application"); +} + +fn build_root_widget() -> impl Widget { + ZStack::new( + Button::from_label(Label::dynamic(|state: &State, _|format!("Very large button with text! Count up (currently {})", state.counter))) + .on_click(|_, state: &mut State, _|state.counter += 1) + ).with_child_at_index( + Button::new("Reset").on_click(|_, state: &mut State, _|state.counter = 0), + 0, + + ) +} diff --git a/druid/src/core.rs b/druid/src/core.rs index 96e00d7ae7..be7af75924 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -740,7 +740,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - Some(mouse_event.pos), + if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, data, env, ); @@ -759,7 +759,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - Some(mouse_event.pos), + if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, data, env, ); @@ -778,7 +778,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - Some(mouse_event.pos), + if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, data, env, ); @@ -800,7 +800,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - Some(mouse_event.pos), + if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, data, env, ); diff --git a/druid/src/event.rs b/druid/src/event.rs index c060e083af..613ac6b77d 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -409,6 +409,33 @@ impl Event { } } + /// Changes the event for widgets which are hidden behind other widgets. + pub fn set_obstructed(&self) -> Self { + match self { + Event::MouseDown(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseDown(me) + } + Event::MouseUp(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseUp(me) + } + Event::MouseMove(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseMove(me) + } + Event::Wheel(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::Wheel(me) + } + _ => self.clone(), + } + } + /// Whether this event should be sent to widgets which are currently not visible and not /// accessible. /// diff --git a/druid/src/mouse.rs b/druid/src/mouse.rs index bdb2c5ae9b..90a95f2d3e 100644 --- a/druid/src/mouse.rs +++ b/druid/src/mouse.rs @@ -69,6 +69,12 @@ pub struct MouseEvent { /// /// [WheelEvent]: https://w3c.github.io/uievents/#event-type-wheel pub wheel_delta: Vec2, + + /// The point of this event is obstructed by another widget. + /// + /// This property is important for overlapping widgets which are active to determine whether + /// they are hot. + pub obstructed: bool, } impl From for MouseEvent { @@ -91,6 +97,7 @@ impl From for MouseEvent { focus, button, wheel_delta, + obstructed: false, } } } diff --git a/druid/src/tests/mod.rs b/druid/src/tests/mod.rs index 4cb8fefbd8..9df2dfea13 100644 --- a/druid/src/tests/mod.rs +++ b/druid/src/tests/mod.rs @@ -48,6 +48,7 @@ pub fn move_mouse(p: impl Into) -> MouseEvent { focus: false, button: MouseButton::None, wheel_delta: Vec2::ZERO, + obstructed: false, } } @@ -63,6 +64,7 @@ pub fn scroll_mouse(p: impl Into, delta: impl Into) -> MouseEvent { focus: false, button: MouseButton::None, wheel_delta: delta.into(), + obstructed: false, } } diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 9523b54840..7ab633371c 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -63,6 +63,7 @@ mod view_switcher; #[allow(clippy::module_inception)] mod widget; mod widget_ext; +mod z_stack; pub use self::image::Image; pub use added::Added; @@ -109,6 +110,7 @@ pub use widget::{Widget, WidgetId}; #[doc(hidden)] pub use widget_ext::WidgetExt; pub use widget_wrapper::WidgetWrapper; +pub use z_stack::{ZStack}; /// The types required to implement a `Widget`. /// diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs new file mode 100644 index 0000000000..b1e2531d50 --- /dev/null +++ b/druid/src/widget/z_stack.rs @@ -0,0 +1,92 @@ +use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Rect}; + +pub struct ZStack { + layers: Vec>, + base_layer: usize, + current_hot: Option, +} + +struct ZChild { + child: WidgetPod>>, +} + +impl ZStack { + pub fn new(base_layer: impl Widget + 'static) -> Self { + Self { + layers: vec![ZChild{ + child: WidgetPod::new(base_layer.boxed()) + }], + base_layer: 0, + current_hot: None, + } + } + + pub fn with_child_at_index(mut self, child: impl Widget + 'static, index: usize) -> Self { + self.layers.insert(index, ZChild { + child: WidgetPod::new(child.boxed()), + }); + if index < self.base_layer { + // Baselayer moves down + self.base_layer += 1; + } + self + } +} + +impl Widget for ZStack { + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) { + let is_pointer_event = matches!(event, Event::MouseDown(_) | Event::MouseMove(_) | Event::MouseUp(_) | Event::Wheel(_)); + let mut previous_child_hot = false; + + self.current_hot = None; + + for (index, layer) in self.layers.iter_mut().enumerate() { + if is_pointer_event && previous_child_hot { + layer.child.event(ctx, &event.set_obstructed(), data, env); + } else { + layer.child.event(ctx, event, data, env); + } + if layer.child.is_hot() { + self.current_hot = Some(index); + previous_child_hot = true; + } + } + } + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) { + for layer in self.layers.iter_mut() { + layer.child.lifecycle(ctx, event, data, env); + } + } + + fn update(&mut self, ctx: &mut UpdateCtx, _: &T, data: &T, env: &Env) { + for layer in self.layers.iter_mut().rev() { + layer.child.update(ctx, data, env); + } + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size { + let mut layout_rect = Rect::ZERO; + + for (index, layer) in self.layers.iter_mut().enumerate() { + let mut inner_ctx = LayoutCtx { + state: ctx.state, + widget_state: ctx.widget_state, + mouse_pos: if Some(index) == self.current_hot { ctx.mouse_pos } else { None }, + }; + + let size = layer.child.layout(&mut inner_ctx, &bc.loosen(), data, env); + layer.child.set_origin(&mut inner_ctx, data, env, Point::ORIGIN); + + layout_rect = layout_rect.union(size.to_rect()); + } + + layout_rect.size() + } + + fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { + for layer in self.layers.iter_mut().rev() { + layer.child.paint(ctx, data, env); + } + } +} \ No newline at end of file From 3cf31a6fa7e7ebdc62ac793d6ce91071949e3f5d Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 21:26:48 +0200 Subject: [PATCH 02/11] changed layout --- druid/examples/z_stack.rs | 5 ++- druid/src/widget/mod.rs | 2 +- druid/src/widget/z_stack.rs | 84 +++++++++++++++++++++++++++++++++---- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs index 97d2c92bd5..3f83100369 100644 --- a/druid/examples/z_stack.rs +++ b/druid/examples/z_stack.rs @@ -19,7 +19,7 @@ #![windows_subsystem = "windows"] use druid::widget::prelude::*; -use druid::widget::{Button, Flex, Label, TextBox, ZStack}; +use druid::widget::{Button, Flex, Label, LinearVec2, TextBox, ZStack}; use druid::{AppLauncher, Data, Lens, UnitPoint, WidgetExt, WindowDesc}; const VERTICAL_WIDGET_SPACING: f64 = 20.0; @@ -54,6 +54,9 @@ fn build_root_widget() -> impl Widget { .on_click(|_, state: &mut State, _|state.counter += 1) ).with_child_at_index( Button::new("Reset").on_click(|_, state: &mut State, _|state.counter = 0), + LinearVec2::new((0.0, 0.5), (10.0, 0.0)), + LinearVec2::full(), + 0, ) diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 7ab633371c..417921c451 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -110,7 +110,7 @@ pub use widget::{Widget, WidgetId}; #[doc(hidden)] pub use widget_ext::WidgetExt; pub use widget_wrapper::WidgetWrapper; -pub use z_stack::{ZStack}; +pub use z_stack::{ZStack, LinearVec2}; /// The types required to implement a `Widget`. /// diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index b1e2531d50..709f504546 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -1,4 +1,4 @@ -use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Rect}; +use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Rect, Vec2}; pub struct ZStack { layers: Vec>, @@ -8,22 +8,33 @@ pub struct ZStack { struct ZChild { child: WidgetPod>>, + size: LinearVec2, + position: LinearVec2, +} + +pub struct LinearVec2 { + pub relative: Vec2, + pub absolute: Vec2, } impl ZStack { pub fn new(base_layer: impl Widget + 'static) -> Self { Self { layers: vec![ZChild{ - child: WidgetPod::new(base_layer.boxed()) + child: WidgetPod::new(base_layer.boxed()), + size: LinearVec2::full(), + position: LinearVec2::empty(), }], base_layer: 0, current_hot: None, } } - pub fn with_child_at_index(mut self, child: impl Widget + 'static, index: usize) -> Self { + pub fn with_child_at_index(mut self, child: impl Widget + 'static, position: LinearVec2, size: LinearVec2, index: usize) -> Self { self.layers.insert(index, ZChild { child: WidgetPod::new(child.boxed()), + position, + size, }); if index < self.base_layer { // Baselayer moves down @@ -66,7 +77,16 @@ impl Widget for ZStack { } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size { - let mut layout_rect = Rect::ZERO; + let base_layer = &mut self.layers[self.base_layer]; + let mut inner_ctx = LayoutCtx { + state: ctx.state, + widget_state: ctx.widget_state, + mouse_pos: if Some(self.base_layer) == self.current_hot { ctx.mouse_pos } else { None }, + }; + + let base_size = base_layer.child.layout(&mut inner_ctx, bc, data, env); + base_layer.child.set_origin(&mut inner_ctx, data, env, Point::ORIGIN); + ctx.set_baseline_offset(base_layer.child.baseline_offset()); for (index, layer) in self.layers.iter_mut().enumerate() { let mut inner_ctx = LayoutCtx { @@ -75,13 +95,14 @@ impl Widget for ZStack { mouse_pos: if Some(index) == self.current_hot { ctx.mouse_pos } else { None }, }; - let size = layer.child.layout(&mut inner_ctx, &bc.loosen(), data, env); - layer.child.set_origin(&mut inner_ctx, data, env, Point::ORIGIN); - - layout_rect = layout_rect.union(size.to_rect()); + let max_size = layer.size.resolve(base_size.to_vec2()); + let size = layer.child.layout(&mut inner_ctx, &BoxConstraints::new(Size::ZERO, max_size.to_size()), data, env); + let remaining = (base_size - size).to_vec2(); + let origin = layer.position.resolve(remaining); + layer.child.set_origin(&mut inner_ctx, data, env, origin.to_point()); } - layout_rect.size() + base_size } fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { @@ -89,4 +110,49 @@ impl Widget for ZStack { layer.child.paint(ctx, data, env); } } +} + +impl LinearVec2 { + pub fn new(relative: impl Into, absolute: impl Into) -> Self { + Self { + relative: relative.into(), + absolute: absolute.into(), + } + } + + pub fn full() -> Self { + Self { + relative: Vec2::new(1.0, 1.0), + absolute: Vec2::new(0.0, 0.0), + } + } + + pub fn empty() -> Self { + Self { + relative: Vec2::new(0.0, 0.0), + absolute: Vec2::new(0.0, 0.0), + } + } + + pub fn from_absolute(absolute: impl Into) -> Self { + Self::new(Vec2::ZERO, absolute) + } + + pub fn from_relative(relative: impl Into) -> Self { + Self::new(relative, Vec2::ZERO) + } + + pub fn resolve(&self, reference: Vec2) -> Vec2 { + Vec2::new( + self.absolute.x + self.relative.x * reference.x, + self.absolute.y + self.relative.y * reference.y + ) + } + + pub fn resolve_external(&self, reference: Vec2) -> Vec2 { + Vec2::new( + (reference.x + self.absolute.x) * self.relative.x / (1.0 - self.relative.x), + (reference.y + self.absolute.y) * self.relative.y / (1.0 - self.relative.y), + ) + } } \ No newline at end of file From c0bfdff4adff188104182189eea89233c8a1873d Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 21:36:04 +0200 Subject: [PATCH 03/11] revert changes to shello.rs --- druid-shell/examples/shello.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/druid-shell/examples/shello.rs b/druid-shell/examples/shello.rs index 804ccdf684..5d197aec7a 100644 --- a/druid-shell/examples/shello.rs +++ b/druid-shell/examples/shello.rs @@ -22,6 +22,9 @@ use druid_shell::{ Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; +const BG_COLOR: Color = Color::rgb8(0x27, 0x28, 0x22); +const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); + #[derive(Default)] struct HelloState { size: Size, @@ -35,8 +38,10 @@ impl WinHandler for HelloState { fn prepare_paint(&mut self) {} - fn paint(&mut self, _: &Region) { - println!("paint"); + fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { + let rect = self.size.to_rect(); + piet.fill(rect, &BG_COLOR); + piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0); } fn command(&mut self, id: u32) { @@ -166,4 +171,4 @@ fn main() { window.show(); app.run(None); -} +} \ No newline at end of file From 0e2753e4ecfc9ef6389ab3e465c190272ffac9c4 Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 21:46:24 +0200 Subject: [PATCH 04/11] updated CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59bd732506..36511dbd41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,8 @@ You can find its changes [documented below](#070---2021-01-01). - Make `Parse` work better with floats and similar types ([#2148] by [@superfell]) - Added `compute_max_intrinsic` method to the `Widget` trait, which determines the maximum useful dimension of the widget ([#2172] by [@sjoshid]) - Windows: Dark mode support for the title bar ([#2196] by [@dristic]) +- `ZStack` widget ([#2235] by [@xarvic]) +- `obstructed` flag to `MouseEvent` ([#2235] by [@xarvic]) ### Changed @@ -102,6 +104,7 @@ You can find its changes [documented below](#070---2021-01-01). - `ClipBox`, `Flex`, `List` and `Split` only call layout on children which need it ([#2145] by [@xarvic]) - `SizedBox` now supports using `Key` for specifying size ([#2151] by [@GoldsteinE]) - `RadioGroup` widgets are now constructed with new `row()`, `column()`, and `for_axis()` methods ([#2157] by [@twitchyliquid64]) +- `WidgetPod::event` stops proagating obstructed mouse events to non active widgets ([#2235] by [@xarvic]) ### Deprecated @@ -850,6 +853,7 @@ Last release without a changelog :( [#2158]: https://github.com/linebender/druid/pull/2158 [#2172]: https://github.com/linebender/druid/pull/2172 [#2196]: https://github.com/linebender/druid/pull/2196 +[#2235]: https://github.com/linebender/druid/pull/2235 [Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master [0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0 From b7ed9e5982f50fd1453add5b8d358500d3b99e8d Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 22:06:33 +0200 Subject: [PATCH 05/11] fix layout --- druid/examples/z_stack.rs | 4 ++-- druid/src/widget/z_stack.rs | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs index 3f83100369..694e2c8201 100644 --- a/druid/examples/z_stack.rs +++ b/druid/examples/z_stack.rs @@ -19,8 +19,8 @@ #![windows_subsystem = "windows"] use druid::widget::prelude::*; -use druid::widget::{Button, Flex, Label, LinearVec2, TextBox, ZStack}; -use druid::{AppLauncher, Data, Lens, UnitPoint, WidgetExt, WindowDesc}; +use druid::widget::{Button, Label, LinearVec2, ZStack}; +use druid::{AppLauncher, Data, Lens, WidgetExt, WindowDesc}; const VERTICAL_WIDGET_SPACING: f64 = 20.0; const TEXT_BOX_WIDTH: f64 = 200.0; diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index 709f504546..abb6280bb1 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -1,4 +1,4 @@ -use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Rect, Vec2}; +use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2}; pub struct ZStack { layers: Vec>, @@ -36,7 +36,7 @@ impl ZStack { position, size, }); - if index < self.base_layer { + if index <= self.base_layer { // Baselayer moves down self.base_layer += 1; } @@ -89,17 +89,19 @@ impl Widget for ZStack { ctx.set_baseline_offset(base_layer.child.baseline_offset()); for (index, layer) in self.layers.iter_mut().enumerate() { - let mut inner_ctx = LayoutCtx { - state: ctx.state, - widget_state: ctx.widget_state, - mouse_pos: if Some(index) == self.current_hot { ctx.mouse_pos } else { None }, - }; - - let max_size = layer.size.resolve(base_size.to_vec2()); - let size = layer.child.layout(&mut inner_ctx, &BoxConstraints::new(Size::ZERO, max_size.to_size()), data, env); - let remaining = (base_size - size).to_vec2(); - let origin = layer.position.resolve(remaining); - layer.child.set_origin(&mut inner_ctx, data, env, origin.to_point()); + if index != self.base_layer { + let mut inner_ctx = LayoutCtx { + state: ctx.state, + widget_state: ctx.widget_state, + mouse_pos: if Some(index) == self.current_hot { ctx.mouse_pos } else { None }, + }; + + let max_size = layer.size.resolve(base_size.to_vec2()); + let size = layer.child.layout(&mut inner_ctx, &BoxConstraints::new(Size::ZERO, max_size.to_size()), data, env); + let remaining = (base_size - size).to_vec2(); + let origin = layer.position.resolve(remaining); + layer.child.set_origin(&mut inner_ctx, data, env, origin.to_point()); + } } base_size From 03811230f25bad7dc9615029604333584a094744 Mon Sep 17 00:00:00 2001 From: xarvic Date: Thu, 28 Jul 2022 22:07:57 +0200 Subject: [PATCH 06/11] fix layout --- druid-shell/examples/shello.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid-shell/examples/shello.rs b/druid-shell/examples/shello.rs index 5d197aec7a..b0e09b6bb7 100644 --- a/druid-shell/examples/shello.rs +++ b/druid-shell/examples/shello.rs @@ -171,4 +171,4 @@ fn main() { window.show(); app.run(None); -} \ No newline at end of file +} From edc319d67ff083cfdf7d4d3d4305559783d90306 Mon Sep 17 00:00:00 2001 From: xarvic Date: Fri, 29 Jul 2022 19:30:27 +0200 Subject: [PATCH 07/11] Added obstructed method to LayoutCtx fixed warnings --- CHANGELOG.md | 2 +- druid/examples/z_stack.rs | 12 +-- druid/src/contexts.rs | 14 +++- druid/src/core.rs | 4 + druid/src/event.rs | 53 ++++++------ druid/src/widget/z_stack.rs | 159 ++++++++++++++++++------------------ 6 files changed, 131 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36511dbd41..6459600e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,7 +74,7 @@ You can find its changes [documented below](#070---2021-01-01). - Added `compute_max_intrinsic` method to the `Widget` trait, which determines the maximum useful dimension of the widget ([#2172] by [@sjoshid]) - Windows: Dark mode support for the title bar ([#2196] by [@dristic]) - `ZStack` widget ([#2235] by [@xarvic]) -- `obstructed` flag to `MouseEvent` ([#2235] by [@xarvic]) +- `obstructed` flag to `MouseEvent` and `set_obstructed` methods to `LayoutCtx` and `Event` ([#2235] by [@xarvic]) ### Changed diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs index 694e2c8201..bae9579236 100644 --- a/druid/examples/z_stack.rs +++ b/druid/examples/z_stack.rs @@ -12,18 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! This is a very small example of how to setup a druid application. -//! It does the almost bare minimum while still being useful. +//! // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] use druid::widget::prelude::*; use druid::widget::{Button, Label, LinearVec2, ZStack}; -use druid::{AppLauncher, Data, Lens, WidgetExt, WindowDesc}; - -const VERTICAL_WIDGET_SPACING: f64 = 20.0; -const TEXT_BOX_WIDTH: f64 = 200.0; +use druid::{AppLauncher, Data, Lens, UnitPoint, WindowDesc}; #[derive(Clone, Data, Lens)] struct State { @@ -54,8 +50,8 @@ fn build_root_widget() -> impl Widget { .on_click(|_, state: &mut State, _|state.counter += 1) ).with_child_at_index( Button::new("Reset").on_click(|_, state: &mut State, _|state.counter = 0), - LinearVec2::new((0.0, 0.5), (10.0, 0.0)), - LinearVec2::full(), + LinearVec2::new(UnitPoint::LEFT, (10.0, 0.0)), + LinearVec2::from_unit(UnitPoint::BOTTOM_RIGHT), 0, diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index d5f39fb4ba..08a66081df 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -854,7 +854,7 @@ impl LifeCycleCtx<'_, '_> { } } -impl LayoutCtx<'_, '_> { +impl<'a, 'b> LayoutCtx<'a, 'b> { /// Set explicit paint [`Insets`] for this widget. /// /// You are not required to set explicit paint bounds unless you need @@ -885,6 +885,18 @@ impl LayoutCtx<'_, '_> { trace!("set_baseline_offset {}", baseline); self.widget_state.baseline_offset = baseline } + + /// Creates a new LayoutCtx for a widget that maybe is hidden by another widget. + /// + /// If obstructed is `true` the child will not set its hot state to `true` even if the cursor + /// is inside its bounds. + pub fn set_obstructed<'c>(&'c mut self, obstructed: bool) -> LayoutCtx<'c, 'b> { + LayoutCtx { + state: &mut self.state, + widget_state: &mut self.widget_state, + mouse_pos: if obstructed { None } else { self.mouse_pos } + } + } } impl PaintCtx<'_, '_, '_> { diff --git a/druid/src/core.rs b/druid/src/core.rs index be7af75924..da22b3cb4a 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -574,6 +574,10 @@ impl> WidgetPod { self.state.needs_window_origin = false; self.state.is_expecting_set_origin_call = true; + //TODO: this does not work! + // self.layout_rect().origin().to_vec2() + self.viewport_offset() is the old position which + // changes after set origin. Therefore changing hot state must happen after the root widget + // set its origin. let child_mouse_pos = ctx .mouse_pos .map(|pos| pos - self.layout_rect().origin().to_vec2() + self.viewport_offset()); diff --git a/druid/src/event.rs b/druid/src/event.rs index 613ac6b77d..960919ce60 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -409,30 +409,37 @@ impl Event { } } - /// Changes the event for widgets which are hidden behind other widgets. - pub fn set_obstructed(&self) -> Self { - match self { - Event::MouseDown(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseDown(me) - } - Event::MouseUp(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseUp(me) - } - Event::MouseMove(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseMove(me) - } - Event::Wheel(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::Wheel(me) + /// Changes the event for widgets which maybe are hidden behind other widgets. + /// + /// If obstructed is `true` the child will not set its hot state to `true` even if the cursor + /// is inside its bounds. + pub fn set_obstructed(&self, obstructed: bool) -> Self { + if obstructed { + match self { + Event::MouseDown(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseDown(me) + } + Event::MouseUp(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseUp(me) + } + Event::MouseMove(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::MouseMove(me) + } + Event::Wheel(me) => { + let mut me = me.clone(); + me.obstructed = true; + Event::Wheel(me) + } + _ => self.clone(), } - _ => self.clone(), + } else { + self.clone() } } diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index abb6280bb1..b15549ce53 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -1,9 +1,11 @@ -use crate::{Data, Point, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2}; +use crate::{Data, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2, UnitPoint, Rect}; +/// A container that stacks its children on top of each other. +/// +/// The container has a baselayer which has the lowest z-index and determines the size of the +/// container. pub struct ZStack { layers: Vec>, - base_layer: usize, - current_hot: Option, } struct ZChild { @@ -12,36 +14,46 @@ struct ZChild { position: LinearVec2, } +/// A two dimensional Vector relative to the available space. pub struct LinearVec2 { - pub relative: Vec2, + pub relative: UnitPoint, pub absolute: Vec2, } impl ZStack { + /// Creates a new ZStack with a baselayer. + /// + /// The baselayer is used by the ZStack to determine its own size. pub fn new(base_layer: impl Widget + 'static) -> Self { Self { layers: vec![ZChild{ child: WidgetPod::new(base_layer.boxed()), - size: LinearVec2::full(), - position: LinearVec2::empty(), + size: LinearVec2::from_unit(UnitPoint::BOTTOM_RIGHT), + position: LinearVec2::from_unit(UnitPoint::TOP_LEFT), }], - base_layer: 0, - current_hot: None, } } + /// Builder-style method to insert a new child into the Z-Stack. + /// + /// The index must be smaller that that of the base-layer. pub fn with_child_at_index(mut self, child: impl Widget + 'static, position: LinearVec2, size: LinearVec2, index: usize) -> Self { + assert!(index < self.layers.len()); self.layers.insert(index, ZChild { child: WidgetPod::new(child.boxed()), position, size, }); - if index <= self.base_layer { - // Baselayer moves down - self.base_layer += 1; - } self } + + /// Builder-style method to add a new child to the Z-Stack. + /// + /// The child is added directly above the base layer. + pub fn with_child(self, child: impl Widget + 'static, position: LinearVec2, size: LinearVec2) -> Self { + let next_index = self.layers.len() - 1; + self.with_child_at_index(child, position, size, next_index) + } } impl Widget for ZStack { @@ -49,18 +61,15 @@ impl Widget for ZStack { let is_pointer_event = matches!(event, Event::MouseDown(_) | Event::MouseMove(_) | Event::MouseUp(_) | Event::Wheel(_)); let mut previous_child_hot = false; - self.current_hot = None; - - for (index, layer) in self.layers.iter_mut().enumerate() { - if is_pointer_event && previous_child_hot { - layer.child.event(ctx, &event.set_obstructed(), data, env); - } else { - layer.child.event(ctx, event, data, env); - } - if layer.child.is_hot() { - self.current_hot = Some(index); - previous_child_hot = true; - } + for layer in self.layers.iter_mut() { + layer.child.event( + ctx, + &event.set_obstructed(is_pointer_event && previous_child_hot), + data, + env + ); + + previous_child_hot |= layer.child.is_hot(); } } @@ -77,37 +86,47 @@ impl Widget for ZStack { } fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size { - let base_layer = &mut self.layers[self.base_layer]; - let mut inner_ctx = LayoutCtx { - state: ctx.state, - widget_state: ctx.widget_state, - mouse_pos: if Some(self.base_layer) == self.current_hot { ctx.mouse_pos } else { None }, - }; - - let base_size = base_layer.child.layout(&mut inner_ctx, bc, data, env); - base_layer.child.set_origin(&mut inner_ctx, data, env, Point::ORIGIN); - ctx.set_baseline_offset(base_layer.child.baseline_offset()); - - for (index, layer) in self.layers.iter_mut().enumerate() { - if index != self.base_layer { - let mut inner_ctx = LayoutCtx { - state: ctx.state, - widget_state: ctx.widget_state, - mouse_pos: if Some(index) == self.current_hot { ctx.mouse_pos } else { None }, - }; - - let max_size = layer.size.resolve(base_size.to_vec2()); - let size = layer.child.layout(&mut inner_ctx, &BoxConstraints::new(Size::ZERO, max_size.to_size()), data, env); - let remaining = (base_size - size).to_vec2(); - let origin = layer.position.resolve(remaining); - layer.child.set_origin(&mut inner_ctx, data, env, origin.to_point()); - } + //Layout base layer + + let base_layer = self.layers.last_mut().unwrap(); + let base_size = base_layer.child.layout(ctx, bc, data, env); + + //Layout other layers + let other_layers = self.layers.len() - 1; + + for layer in self.layers.iter_mut().take(other_layers) { + let max_size = layer.size.resolve(base_size); + layer.child.layout( + ctx, + &BoxConstraints::new(Size::ZERO, max_size.to_size()), + data, + env + ); + } + + //Set origin for all Layers and calculate paint insets + let mut previous_child_hot = false; + let mut paint_rect = Rect::ZERO; + + for layer in self.layers.iter_mut() { + let mut inner_ctx = ctx.set_obstructed(previous_child_hot); + + let remaining = base_size - layer.child.layout_rect().size(); + let origin = layer.position.resolve(remaining).to_point(); + layer.child.set_origin(&mut inner_ctx, data, env, origin); + + paint_rect = paint_rect.union(layer.child.paint_rect()); + previous_child_hot |= layer.child.is_hot(); } + ctx.set_paint_insets(paint_rect - base_size.to_rect()); + ctx.set_baseline_offset(self.layers.last().unwrap().child.baseline_offset()); + base_size } fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { + //Painters algorithm (Painting back to front) for layer in self.layers.iter_mut().rev() { layer.child.paint(ctx, data, env); } @@ -115,46 +134,26 @@ impl Widget for ZStack { } impl LinearVec2 { - pub fn new(relative: impl Into, absolute: impl Into) -> Self { + /// Creates a new LinearVec2 from an UnitPoint and an offset. + pub fn new(relative: impl Into, offset: impl Into) -> Self { Self { relative: relative.into(), - absolute: absolute.into(), - } - } - - pub fn full() -> Self { - Self { - relative: Vec2::new(1.0, 1.0), - absolute: Vec2::new(0.0, 0.0), + absolute: offset.into(), } } - pub fn empty() -> Self { - Self { - relative: Vec2::new(0.0, 0.0), - absolute: Vec2::new(0.0, 0.0), - } + /// creates a new LinearVec2 from a UnitPoint. Offset ist set to Zero. + pub fn from_unit(relative: impl Into) -> Self { + let point = relative.into(); + Self::new(Vec2::new(point.), Vec2::ZERO) } - pub fn from_absolute(absolute: impl Into) -> Self { - Self::new(Vec2::ZERO, absolute) - } - - pub fn from_relative(relative: impl Into) -> Self { - Self::new(relative, Vec2::ZERO) - } + pub fn from_relative_size(relative: impl Into) -> Self { - pub fn resolve(&self, reference: Vec2) -> Vec2 { - Vec2::new( - self.absolute.x + self.relative.x * reference.x, - self.absolute.y + self.relative.y * reference.y - ) } - pub fn resolve_external(&self, reference: Vec2) -> Vec2 { - Vec2::new( - (reference.x + self.absolute.x) * self.relative.x / (1.0 - self.relative.x), - (reference.y + self.absolute.y) * self.relative.y / (1.0 - self.relative.y), - ) + /// resolves this LinearVec2 for a given size + pub fn resolve(&self, reference: Size) -> Vec2 { + self.relative.resolve(reference.to_rect()).to_vec2() + self.absolute } } \ No newline at end of file From 4194b6d0f5a983150de4953a901bc68c28e5084a Mon Sep 17 00:00:00 2001 From: xarvic Date: Fri, 29 Jul 2022 20:04:26 +0200 Subject: [PATCH 08/11] Added obstructed method to LayoutCtx fixed warnings change builder methods for ZStack --- druid/examples/z_stack.rs | 17 +++--- druid/src/widget/mod.rs | 2 +- druid/src/widget/z_stack.rs | 111 ++++++++++++++++++++++-------------- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs index bae9579236..0c794d68c9 100644 --- a/druid/examples/z_stack.rs +++ b/druid/examples/z_stack.rs @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! +//! A simple test of overlapping widgets. // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] use druid::widget::prelude::*; -use druid::widget::{Button, Label, LinearVec2, ZStack}; -use druid::{AppLauncher, Data, Lens, UnitPoint, WindowDesc}; +use druid::widget::{Button, Label, ZStack}; +use druid::{AppLauncher, Data, Lens, UnitPoint, Vec2, WindowDesc}; #[derive(Clone, Data, Lens)] struct State { @@ -48,12 +48,11 @@ fn build_root_widget() -> impl Widget { ZStack::new( Button::from_label(Label::dynamic(|state: &State, _|format!("Very large button with text! Count up (currently {})", state.counter))) .on_click(|_, state: &mut State, _|state.counter += 1) - ).with_child_at_index( + ).with_child( Button::new("Reset").on_click(|_, state: &mut State, _|state.counter = 0), - LinearVec2::new(UnitPoint::LEFT, (10.0, 0.0)), - LinearVec2::from_unit(UnitPoint::BOTTOM_RIGHT), - - 0, - + Vec2::new(1.0, 1.0), + Vec2::ZERO, + UnitPoint::LEFT, + Vec2::new(10.0, 0.0), ) } diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 417921c451..b791e3f9a4 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -110,7 +110,7 @@ pub use widget::{Widget, WidgetId}; #[doc(hidden)] pub use widget_ext::WidgetExt; pub use widget_wrapper::WidgetWrapper; -pub use z_stack::{ZStack, LinearVec2}; +pub use z_stack::ZStack; /// The types required to implement a `Widget`. /// diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index b15549ce53..df123d15e8 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -1,4 +1,4 @@ -use crate::{Data, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2, UnitPoint, Rect}; +use crate::{Data, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2, UnitPoint, Rect, Point}; /// A container that stacks its children on top of each other. /// @@ -10,14 +10,10 @@ pub struct ZStack { struct ZChild { child: WidgetPod>>, - size: LinearVec2, - position: LinearVec2, -} - -/// A two dimensional Vector relative to the available space. -pub struct LinearVec2 { - pub relative: UnitPoint, - pub absolute: Vec2, + relative_size: Vec2, + absolute_size: Vec2, + position: UnitPoint, + offset: Vec2, } impl ZStack { @@ -28,31 +24,72 @@ impl ZStack { Self { layers: vec![ZChild{ child: WidgetPod::new(base_layer.boxed()), - size: LinearVec2::from_unit(UnitPoint::BOTTOM_RIGHT), - position: LinearVec2::from_unit(UnitPoint::TOP_LEFT), + + relative_size: Vec2::new(1.0, 1.0), + absolute_size: Vec2::ZERO, + position: UnitPoint::CENTER, + offset: Vec2::ZERO }], } } - /// Builder-style method to insert a new child into the Z-Stack. + /// Builder-style method to add a new child to the Z-Stack. + /// + /// The child is added directly above the base layer. /// - /// The index must be smaller that that of the base-layer. - pub fn with_child_at_index(mut self, child: impl Widget + 'static, position: LinearVec2, size: LinearVec2, index: usize) -> Self { - assert!(index < self.layers.len()); - self.layers.insert(index, ZChild { + /// `relative_size` is the space the child is allowed to take up relative to its parent. The + /// values are between 0 and 1. + /// `absolute_size` is a fixed amount of pixels added to `relative_size`. + /// + /// `position` is the alignment of the child inside the remaining space of its parent. + /// + /// `offset` is a fixed amount of pixels added to `position`. + pub fn with_child( + mut self, + child: impl Widget + 'static, + relative_size: Vec2, + absolute_size: Vec2, + position: UnitPoint, + offset: Vec2, + ) -> Self { + let next_index = self.layers.len() - 1; + self.layers.insert(next_index, ZChild { child: WidgetPod::new(child.boxed()), + relative_size, + absolute_size, position, - size, + offset, }); self } /// Builder-style method to add a new child to the Z-Stack. /// - /// The child is added directly above the base layer. - pub fn with_child(self, child: impl Widget + 'static, position: LinearVec2, size: LinearVec2) -> Self { - let next_index = self.layers.len() - 1; - self.with_child_at_index(child, position, size, next_index) + /// The child is added directly above the base layer, is positioned in the center and has no + /// size constrains. + pub fn with_centered_child( + self, + child: impl Widget + 'static, + ) -> Self { + self.with_aligned_child(child, UnitPoint::CENTER) + } + + /// Builder-style method to add a new child to the Z-Stack. + /// + /// The child is added directly above the base layer, uses the given alignment and has no + /// size constrains. + pub fn with_aligned_child( + self, + child: impl Widget + 'static, + alignment: UnitPoint, + ) -> Self { + self.with_child( + child, + Vec2::new(1.0, 1.0), + Vec2::ZERO, + alignment, + Vec2::ZERO, + ) } } @@ -95,10 +132,10 @@ impl Widget for ZStack { let other_layers = self.layers.len() - 1; for layer in self.layers.iter_mut().take(other_layers) { - let max_size = layer.size.resolve(base_size); + let max_size = layer.resolve_max_size(base_size); layer.child.layout( ctx, - &BoxConstraints::new(Size::ZERO, max_size.to_size()), + &BoxConstraints::new(Size::ZERO, max_size), data, env ); @@ -112,7 +149,7 @@ impl Widget for ZStack { let mut inner_ctx = ctx.set_obstructed(previous_child_hot); let remaining = base_size - layer.child.layout_rect().size(); - let origin = layer.position.resolve(remaining).to_point(); + let origin = layer.resolve_point(remaining); layer.child.set_origin(&mut inner_ctx, data, env, origin); paint_rect = paint_rect.union(layer.child.paint_rect()); @@ -133,27 +170,13 @@ impl Widget for ZStack { } } -impl LinearVec2 { - /// Creates a new LinearVec2 from an UnitPoint and an offset. - pub fn new(relative: impl Into, offset: impl Into) -> Self { - Self { - relative: relative.into(), - absolute: offset.into(), - } - } - - /// creates a new LinearVec2 from a UnitPoint. Offset ist set to Zero. - pub fn from_unit(relative: impl Into) -> Self { - let point = relative.into(); - Self::new(Vec2::new(point.), Vec2::ZERO) - } - - pub fn from_relative_size(relative: impl Into) -> Self { - +impl ZChild { + fn resolve_max_size(&self, availible: Size) -> Size { + self.absolute_size.to_size() + + Size::new(availible.width * self.relative_size.x, availible.height * self.relative_size.y) } - /// resolves this LinearVec2 for a given size - pub fn resolve(&self, reference: Size) -> Vec2 { - self.relative.resolve(reference.to_rect()).to_vec2() + self.absolute + fn resolve_point(&self, remaining_space: Size) -> Point { + (self.position.resolve(remaining_space.to_rect()).to_vec2() + self.offset).to_point() } } \ No newline at end of file From 5d099134b5890b9f535eda85bd68c56692e41b97 Mon Sep 17 00:00:00 2001 From: xarvic Date: Fri, 29 Jul 2022 20:09:28 +0200 Subject: [PATCH 09/11] reformat --- druid/examples/z_stack.rs | 18 ++++++----- druid/src/contexts.rs | 2 +- druid/src/core.rs | 24 +++++++++++--- druid/src/widget/z_stack.rs | 62 +++++++++++++++++++------------------ 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/druid/examples/z_stack.rs b/druid/examples/z_stack.rs index 0c794d68c9..b736c8e206 100644 --- a/druid/examples/z_stack.rs +++ b/druid/examples/z_stack.rs @@ -33,9 +33,7 @@ pub fn main() { .window_size((400.0, 400.0)); // create the initial app state - let initial_state: State = State { - counter: 0, - }; + let initial_state: State = State { counter: 0 }; // start the application. Here we pass in the application state. AppLauncher::with_window(main_window) @@ -46,10 +44,16 @@ pub fn main() { fn build_root_widget() -> impl Widget { ZStack::new( - Button::from_label(Label::dynamic(|state: &State, _|format!("Very large button with text! Count up (currently {})", state.counter))) - .on_click(|_, state: &mut State, _|state.counter += 1) - ).with_child( - Button::new("Reset").on_click(|_, state: &mut State, _|state.counter = 0), + Button::from_label(Label::dynamic(|state: &State, _| { + format!( + "Very large button with text! Count up (currently {})", + state.counter + ) + })) + .on_click(|_, state: &mut State, _| state.counter += 1), + ) + .with_child( + Button::new("Reset").on_click(|_, state: &mut State, _| state.counter = 0), Vec2::new(1.0, 1.0), Vec2::ZERO, UnitPoint::LEFT, diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index dd4aed582f..f52d6c8531 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -894,7 +894,7 @@ impl<'a, 'b> LayoutCtx<'a, 'b> { LayoutCtx { state: &mut self.state, widget_state: &mut self.widget_state, - mouse_pos: if obstructed { None } else { self.mouse_pos } + mouse_pos: if obstructed { None } else { self.mouse_pos }, } } } diff --git a/druid/src/core.rs b/druid/src/core.rs index e60157dc7e..89f0ccf68a 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -744,7 +744,11 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, + if !mouse_event.obstructed { + Some(mouse_event.pos) + } else { + None + }, data, env, ); @@ -763,7 +767,11 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, + if !mouse_event.obstructed { + Some(mouse_event.pos) + } else { + None + }, data, env, ); @@ -782,7 +790,11 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, + if !mouse_event.obstructed { + Some(mouse_event.pos) + } else { + None + }, data, env, ); @@ -804,7 +816,11 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed {Some(mouse_event.pos)} else {None}, + if !mouse_event.obstructed { + Some(mouse_event.pos) + } else { + None + }, data, env, ); diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index df123d15e8..5fd3d82c18 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -1,4 +1,7 @@ -use crate::{Data, Size, Widget, WidgetPod, WidgetExt, EventCtx, Event, Env, LifeCycleCtx, LifeCycle, UpdateCtx, LayoutCtx, BoxConstraints, PaintCtx, Vec2, UnitPoint, Rect, Point}; +use crate::{ + BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, Rect, Size, UnitPoint, UpdateCtx, Vec2, Widget, WidgetExt, WidgetPod, +}; /// A container that stacks its children on top of each other. /// @@ -22,13 +25,13 @@ impl ZStack { /// The baselayer is used by the ZStack to determine its own size. pub fn new(base_layer: impl Widget + 'static) -> Self { Self { - layers: vec![ZChild{ + layers: vec![ZChild { child: WidgetPod::new(base_layer.boxed()), relative_size: Vec2::new(1.0, 1.0), absolute_size: Vec2::ZERO, position: UnitPoint::CENTER, - offset: Vec2::ZERO + offset: Vec2::ZERO, }], } } @@ -53,13 +56,16 @@ impl ZStack { offset: Vec2, ) -> Self { let next_index = self.layers.len() - 1; - self.layers.insert(next_index, ZChild { - child: WidgetPod::new(child.boxed()), - relative_size, - absolute_size, - position, - offset, - }); + self.layers.insert( + next_index, + ZChild { + child: WidgetPod::new(child.boxed()), + relative_size, + absolute_size, + position, + offset, + }, + ); self } @@ -67,10 +73,7 @@ impl ZStack { /// /// The child is added directly above the base layer, is positioned in the center and has no /// size constrains. - pub fn with_centered_child( - self, - child: impl Widget + 'static, - ) -> Self { + pub fn with_centered_child(self, child: impl Widget + 'static) -> Self { self.with_aligned_child(child, UnitPoint::CENTER) } @@ -78,11 +81,7 @@ impl ZStack { /// /// The child is added directly above the base layer, uses the given alignment and has no /// size constrains. - pub fn with_aligned_child( - self, - child: impl Widget + 'static, - alignment: UnitPoint, - ) -> Self { + pub fn with_aligned_child(self, child: impl Widget + 'static, alignment: UnitPoint) -> Self { self.with_child( child, Vec2::new(1.0, 1.0), @@ -95,7 +94,10 @@ impl ZStack { impl Widget for ZStack { fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) { - let is_pointer_event = matches!(event, Event::MouseDown(_) | Event::MouseMove(_) | Event::MouseUp(_) | Event::Wheel(_)); + let is_pointer_event = matches!( + event, + Event::MouseDown(_) | Event::MouseMove(_) | Event::MouseUp(_) | Event::Wheel(_) + ); let mut previous_child_hot = false; for layer in self.layers.iter_mut() { @@ -103,7 +105,7 @@ impl Widget for ZStack { ctx, &event.set_obstructed(is_pointer_event && previous_child_hot), data, - env + env, ); previous_child_hot |= layer.child.is_hot(); @@ -133,12 +135,9 @@ impl Widget for ZStack { for layer in self.layers.iter_mut().take(other_layers) { let max_size = layer.resolve_max_size(base_size); - layer.child.layout( - ctx, - &BoxConstraints::new(Size::ZERO, max_size), - data, - env - ); + layer + .child + .layout(ctx, &BoxConstraints::new(Size::ZERO, max_size), data, env); } //Set origin for all Layers and calculate paint insets @@ -172,11 +171,14 @@ impl Widget for ZStack { impl ZChild { fn resolve_max_size(&self, availible: Size) -> Size { - self.absolute_size.to_size() + - Size::new(availible.width * self.relative_size.x, availible.height * self.relative_size.y) + self.absolute_size.to_size() + + Size::new( + availible.width * self.relative_size.x, + availible.height * self.relative_size.y, + ) } fn resolve_point(&self, remaining_space: Size) -> Point { (self.position.resolve(remaining_space.to_rect()).to_vec2() + self.offset).to_point() } -} \ No newline at end of file +} From 3dae92f9966350bfb223f6d2f24b049285b6b683 Mon Sep 17 00:00:00 2001 From: xarvic Date: Wed, 3 Aug 2022 15:52:16 +0200 Subject: [PATCH 10/11] use is_handled instead of is_obstructed. --- druid/src/contexts.rs | 6 +++--- druid/src/core.rs | 15 ++++++++++----- druid/src/event.rs | 34 ---------------------------------- druid/src/mouse.rs | 7 ------- druid/src/tests/mod.rs | 2 -- druid/src/widget/z_stack.rs | 14 +++++--------- 6 files changed, 18 insertions(+), 60 deletions(-) diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index f52d6c8531..74fa360b8f 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -888,13 +888,13 @@ impl<'a, 'b> LayoutCtx<'a, 'b> { /// Creates a new LayoutCtx for a widget that maybe is hidden by another widget. /// - /// If obstructed is `true` the child will not set its hot state to `true` even if the cursor + /// If ignore is `true` the child will not set its hot state to `true` even if the cursor /// is inside its bounds. - pub fn set_obstructed<'c>(&'c mut self, obstructed: bool) -> LayoutCtx<'c, 'b> { + pub fn ignore_hot<'c>(&'c mut self, ignore: bool) -> LayoutCtx<'c, 'b> { LayoutCtx { state: &mut self.state, widget_state: &mut self.widget_state, - mouse_pos: if obstructed { None } else { self.mouse_pos }, + mouse_pos: if ignore { None } else { self.mouse_pos }, } } } diff --git a/druid/src/core.rs b/druid/src/core.rs index 89f0ccf68a..4f32aa701c 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -666,7 +666,12 @@ impl> WidgetPod { } // TODO: factor as much logic as possible into monomorphic functions. - if ctx.is_handled { + if ctx.is_handled + && !matches!( + event, + Event::MouseDown(_) | Event::MouseUp(_) | Event::MouseMove(_) | Event::Wheel(_) + ) + { // This function is called by containers to propagate an event from // containers to children. Non-recurse events will be invoked directly // from other points in the library. @@ -744,7 +749,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed { + if !ctx.is_handled { Some(mouse_event.pos) } else { None @@ -767,7 +772,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed { + if !ctx.is_handled { Some(mouse_event.pos) } else { None @@ -790,7 +795,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed { + if !ctx.is_handled { Some(mouse_event.pos) } else { None @@ -816,7 +821,7 @@ impl> WidgetPod { &mut self.state, ctx.state, rect, - if !mouse_event.obstructed { + if !ctx.is_handled { Some(mouse_event.pos) } else { None diff --git a/druid/src/event.rs b/druid/src/event.rs index 0198e3388d..e45df871e3 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -409,40 +409,6 @@ impl Event { } } - /// Changes the event for widgets which maybe are hidden behind other widgets. - /// - /// If obstructed is `true` the child will not set its hot state to `true` even if the cursor - /// is inside its bounds. - pub fn set_obstructed(&self, obstructed: bool) -> Self { - if obstructed { - match self { - Event::MouseDown(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseDown(me) - } - Event::MouseUp(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseUp(me) - } - Event::MouseMove(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::MouseMove(me) - } - Event::Wheel(me) => { - let mut me = me.clone(); - me.obstructed = true; - Event::Wheel(me) - } - _ => self.clone(), - } - } else { - self.clone() - } - } - /// Whether this event should be sent to widgets which are currently not visible and not /// accessible. /// diff --git a/druid/src/mouse.rs b/druid/src/mouse.rs index 90a95f2d3e..bdb2c5ae9b 100644 --- a/druid/src/mouse.rs +++ b/druid/src/mouse.rs @@ -69,12 +69,6 @@ pub struct MouseEvent { /// /// [WheelEvent]: https://w3c.github.io/uievents/#event-type-wheel pub wheel_delta: Vec2, - - /// The point of this event is obstructed by another widget. - /// - /// This property is important for overlapping widgets which are active to determine whether - /// they are hot. - pub obstructed: bool, } impl From for MouseEvent { @@ -97,7 +91,6 @@ impl From for MouseEvent { focus, button, wheel_delta, - obstructed: false, } } } diff --git a/druid/src/tests/mod.rs b/druid/src/tests/mod.rs index 474644d5a8..bfe352be4a 100644 --- a/druid/src/tests/mod.rs +++ b/druid/src/tests/mod.rs @@ -48,7 +48,6 @@ pub fn move_mouse(p: impl Into) -> MouseEvent { focus: false, button: MouseButton::None, wheel_delta: Vec2::ZERO, - obstructed: false, } } @@ -64,7 +63,6 @@ pub fn scroll_mouse(p: impl Into, delta: impl Into) -> MouseEvent { focus: false, button: MouseButton::None, wheel_delta: delta.into(), - obstructed: false, } } diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index 5fd3d82c18..8f1d721895 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -98,17 +98,13 @@ impl Widget for ZStack { event, Event::MouseDown(_) | Event::MouseMove(_) | Event::MouseUp(_) | Event::Wheel(_) ); - let mut previous_child_hot = false; for layer in self.layers.iter_mut() { - layer.child.event( - ctx, - &event.set_obstructed(is_pointer_event && previous_child_hot), - data, - env, - ); + layer.child.event(ctx, event, data, env); - previous_child_hot |= layer.child.is_hot(); + if is_pointer_event && layer.child.is_hot() { + ctx.set_handled(); + } } } @@ -145,7 +141,7 @@ impl Widget for ZStack { let mut paint_rect = Rect::ZERO; for layer in self.layers.iter_mut() { - let mut inner_ctx = ctx.set_obstructed(previous_child_hot); + let mut inner_ctx = ctx.ignore_hot(previous_child_hot); let remaining = base_size - layer.child.layout_rect().size(); let origin = layer.resolve_point(remaining); From c89102a3325f164386b7df7680d273c94f43d5c8 Mon Sep 17 00:00:00 2001 From: xarvic Date: Wed, 3 Aug 2022 16:16:02 +0200 Subject: [PATCH 11/11] updated CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9287a488..78e8263a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,7 +74,6 @@ You can find its changes [documented below](#070---2021-01-01). - Added `compute_max_intrinsic` method to the `Widget` trait, which determines the maximum useful dimension of the widget ([#2172] by [@sjoshid]) - Windows: Dark mode support for the title bar ([#2196] by [@dristic]) - `ZStack` widget ([#2235] by [@xarvic]) -- `obstructed` flag to `MouseEvent` and `set_obstructed` methods to `LayoutCtx` and `Event` ([#2235] by [@xarvic]) ### Changed @@ -105,7 +104,7 @@ You can find its changes [documented below](#070---2021-01-01). - `SizedBox` now supports using `Key` for specifying size ([#2151] by [@GoldsteinE]) - `RadioGroup` widgets are now constructed with new `row()`, `column()`, and `for_axis()` methods ([#2157] by [@twitchyliquid64]) - Replace `info_span!` with `trace_span!` ([#2203] by [@NickLarsenNZ]) -- `WidgetPod::event` stops proagating obstructed mouse events to non active widgets ([#2235] by [@xarvic]) +- `WidgetPod::event` propagates handled mouse events to active children ([#2235] by [@xarvic]) ### Deprecated