mirror of
https://github.com/danbulant/cushy
synced 2026-07-07 12:10:44 +00:00
Added hover support to OverlayLayer
This commit is contained in:
parent
0d34924ddf
commit
2fe08fc9e9
6 changed files with 192 additions and 56 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -833,6 +833,7 @@ dependencies = [
|
||||||
"kludgine",
|
"kludgine",
|
||||||
"palette",
|
"palette",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
"rand",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|
@ -1711,6 +1712,12 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
|
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "presser"
|
name = "presser"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -1809,6 +1816,18 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1817,6 +1836,9 @@ name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "range-alloc"
|
name = "range-alloc"
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ opt-level = 2
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pollster = "0.3.0"
|
pollster = "0.3.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,60 @@
|
||||||
|
use std::panic::UnwindSafe;
|
||||||
|
|
||||||
use gooey::widget::{MakeWidget, MakeWidgetWithId, WidgetTag};
|
use gooey::widget::{MakeWidget, MakeWidgetWithId, WidgetTag};
|
||||||
use gooey::widgets::layers::OverlayLayer;
|
use gooey::widgets::layers::{OverlayBuilder, OverlayLayer};
|
||||||
use gooey::Run;
|
use gooey::Run;
|
||||||
|
use kludgine::Color;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
fn main() -> gooey::Result {
|
fn main() -> gooey::Result {
|
||||||
let overlay = OverlayLayer::default();
|
let overlay = OverlayLayer::default();
|
||||||
|
|
||||||
test_widget(&overlay)
|
test_widget(&overlay, true)
|
||||||
.centered()
|
.centered()
|
||||||
.and(overlay)
|
.and(overlay)
|
||||||
.into_layers()
|
.into_layers()
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_widget(overlay: &OverlayLayer) -> impl MakeWidget {
|
fn test_widget(overlay: &OverlayLayer, is_root: bool) -> impl MakeWidget {
|
||||||
let (my_tag, my_id) = WidgetTag::new();
|
let (my_tag, my_id) = WidgetTag::new();
|
||||||
let right = "Right".into_button().on_click({
|
let right = show_overlay_button("Right", overlay, move |overlay| overlay.right_of(my_id));
|
||||||
let overlay = overlay.clone();
|
let left = show_overlay_button("Left", overlay, move |overlay| overlay.left_of(my_id));
|
||||||
move |()| {
|
let up = show_overlay_button("Up", overlay, move |overlay| overlay.above(my_id));
|
||||||
overlay
|
let down = show_overlay_button("Down", overlay, move |overlay| overlay.below(my_id));
|
||||||
.build_overlay(test_widget(&overlay))
|
|
||||||
.right_of(my_id)
|
|
||||||
.show()
|
|
||||||
.forget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let left = "Left".into_button().on_click({
|
|
||||||
let overlay = overlay.clone();
|
|
||||||
move |()| {
|
|
||||||
overlay
|
|
||||||
.build_overlay(test_widget(&overlay))
|
|
||||||
.left_of(my_id)
|
|
||||||
.show()
|
|
||||||
.forget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let up = "Up".into_button().on_click({
|
|
||||||
let overlay = overlay.clone();
|
|
||||||
move |()| {
|
|
||||||
overlay
|
|
||||||
.build_overlay(test_widget(&overlay))
|
|
||||||
.above(my_id)
|
|
||||||
.show()
|
|
||||||
.forget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let down = "Down".into_button().on_click({
|
|
||||||
let overlay = overlay.clone();
|
|
||||||
move |()| {
|
|
||||||
overlay
|
|
||||||
.build_overlay(test_widget(&overlay))
|
|
||||||
.below(my_id)
|
|
||||||
.show()
|
|
||||||
.forget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
up.centered()
|
let mut buttons = up
|
||||||
|
.centered()
|
||||||
.and(left.and(right).into_columns())
|
.and(left.and(right).into_columns())
|
||||||
.and(down.centered())
|
.and(down.centered())
|
||||||
.into_rows()
|
.into_rows()
|
||||||
.contain()
|
.contain();
|
||||||
.pad()
|
|
||||||
.make_with_id(my_tag)
|
if !is_root {
|
||||||
|
buttons = buttons.background_color(Color::new(
|
||||||
|
thread_rng().gen(),
|
||||||
|
thread_rng().gen(),
|
||||||
|
thread_rng().gen(),
|
||||||
|
255,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.pad().make_with_id(my_tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_overlay_button(
|
||||||
|
label: &str,
|
||||||
|
overlay: &OverlayLayer,
|
||||||
|
direction_func: impl for<'a> Fn(OverlayBuilder<'a>) -> OverlayBuilder<'a>
|
||||||
|
+ Send
|
||||||
|
+ UnwindSafe
|
||||||
|
+ 'static,
|
||||||
|
) -> impl MakeWidget {
|
||||||
|
let overlay = overlay.clone();
|
||||||
|
label.into_button().on_click(move |()| {
|
||||||
|
direction_func(overlay.build_overlay(test_widget(&overlay, false)))
|
||||||
|
.hide_on_unhover()
|
||||||
|
.show()
|
||||||
|
.forget();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,13 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
let mut cursor = None;
|
let mut cursor = None;
|
||||||
for hover in changes.hovered.into_iter().rev() {
|
for hover in changes.hovered.into_iter().rev() {
|
||||||
let mut context = self.for_other(&hover);
|
let mut context = self.for_other(&hover);
|
||||||
let widget_cursor = hover.lock().as_widget().hover(location, &mut context);
|
let Some(last_layout) = context.last_layout() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let widget_cursor = hover
|
||||||
|
.lock()
|
||||||
|
.as_widget()
|
||||||
|
.hover(location - last_layout.origin, &mut context);
|
||||||
|
|
||||||
if cursor.is_none() {
|
if cursor.is_none() {
|
||||||
cursor = widget_cursor;
|
cursor = widget_cursor;
|
||||||
|
|
@ -330,7 +336,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
let relative = location - widget_layout.origin;
|
let relative = location - widget_layout.origin;
|
||||||
|
|
||||||
if widget_context.hit_test(relative) {
|
if widget_context.hit_test(relative) {
|
||||||
widget_context.hover(relative);
|
widget_context.hover(location);
|
||||||
drop(widget_context);
|
drop(widget_context);
|
||||||
self.cursor.widget = Some(widget.id());
|
self.cursor.widget = Some(widget.id());
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
10
src/value.rs
10
src/value.rs
|
|
@ -536,6 +536,7 @@ impl<T> Dynamic<T> {
|
||||||
DynamicGuard {
|
DynamicGuard {
|
||||||
guard: self.0.state().expect("deadlocked"),
|
guard: self.0.state().expect("deadlocked"),
|
||||||
accessed_mut: false,
|
accessed_mut: false,
|
||||||
|
prevent_notifications: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1100,6 +1101,7 @@ impl<T> DerefMut for GenerationalValue<T> {
|
||||||
pub struct DynamicGuard<'a, T> {
|
pub struct DynamicGuard<'a, T> {
|
||||||
guard: DynamicMutexGuard<'a, T>,
|
guard: DynamicMutexGuard<'a, T>,
|
||||||
accessed_mut: bool,
|
accessed_mut: bool,
|
||||||
|
prevent_notifications: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DynamicGuard<'_, T> {
|
impl<T> DynamicGuard<'_, T> {
|
||||||
|
|
@ -1111,6 +1113,12 @@ impl<T> DynamicGuard<'_, T> {
|
||||||
pub fn generation(&self) -> Generation {
|
pub fn generation(&self) -> Generation {
|
||||||
self.guard.wrapped.generation
|
self.guard.wrapped.generation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prevent any access through [`DerefMut`] from triggering change
|
||||||
|
/// notifications.
|
||||||
|
pub fn prevent_notifications(&mut self) {
|
||||||
|
self.prevent_notifications = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Deref for DynamicGuard<'a, T> {
|
impl<'a, T> Deref for DynamicGuard<'a, T> {
|
||||||
|
|
@ -1130,7 +1138,7 @@ impl<'a, T> DerefMut for DynamicGuard<'a, T> {
|
||||||
|
|
||||||
impl<T> Drop for DynamicGuard<'_, T> {
|
impl<T> Drop for DynamicGuard<'_, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.accessed_mut {
|
if self.accessed_mut && !self.prevent_notifications {
|
||||||
let mut callbacks = Some(self.guard.note_changed());
|
let mut callbacks = Some(self.guard.note_changed());
|
||||||
run_in_bg(move || drop(callbacks.take()));
|
run_in_bg(move || drop(callbacks.take()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use kludgine::figures::units::{Px, UPx};
|
||||||
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size, Zero};
|
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size, Zero};
|
||||||
|
|
||||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||||
use crate::value::{Dynamic, Generation, IntoValue, Value};
|
use crate::value::{Dynamic, DynamicGuard, Generation, IntoValue, Value};
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
Children, MakeWidget, ManagedWidget, OnceCallback, Widget, WidgetId, WidgetRef,
|
Children, MakeWidget, ManagedWidget, OnceCallback, Widget, WidgetId, WidgetRef,
|
||||||
};
|
};
|
||||||
|
|
@ -194,8 +194,7 @@ impl OverlayLayer {
|
||||||
|
|
||||||
impl Widget for OverlayLayer {
|
impl Widget for OverlayLayer {
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||||
let mut guard = self.state.lock();
|
let state = self.state.lock();
|
||||||
let state = &mut *guard;
|
|
||||||
|
|
||||||
for child in &state.overlays {
|
for child in &state.overlays {
|
||||||
let WidgetRef::Mounted(mounted) = &child.widget else {
|
let WidgetRef::Mounted(mounted) = &child.widget else {
|
||||||
|
|
@ -211,8 +210,8 @@ impl Widget for OverlayLayer {
|
||||||
available_space: Size<ConstraintLimit>,
|
available_space: Size<ConstraintLimit>,
|
||||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Size<UPx> {
|
) -> Size<UPx> {
|
||||||
let mut guard = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
let state = &mut *guard;
|
state.prevent_notifications();
|
||||||
|
|
||||||
let available_space = available_space.map(ConstraintLimit::max);
|
let available_space = available_space.map(ConstraintLimit::max);
|
||||||
|
|
||||||
|
|
@ -237,7 +236,7 @@ impl Widget for OverlayLayer {
|
||||||
context.set_child_layout(&widget, layout);
|
context.set_child_layout(&widget, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(guard);
|
drop(state);
|
||||||
|
|
||||||
// Now that we're done mutating state, we can register for invalidation
|
// Now that we're done mutating state, we can register for invalidation
|
||||||
// tracking.
|
// tracking.
|
||||||
|
|
@ -249,15 +248,120 @@ impl Widget for OverlayLayer {
|
||||||
// when shown.
|
// when shown.
|
||||||
Size::ZERO
|
Size::ZERO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hit_test(&mut self, location: Point<Px>, context: &mut EventContext<'_, '_>) -> bool {
|
||||||
|
let state = self.state.lock();
|
||||||
|
if let Some(index) = state.test_point(location, false, context) {
|
||||||
|
index > 0
|
||||||
|
} else {
|
||||||
|
!(state.overlays.is_empty() || state.point_is_in_root_relative(location, context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover(
|
||||||
|
&mut self,
|
||||||
|
location: Point<Px>,
|
||||||
|
context: &mut EventContext<'_, '_>,
|
||||||
|
) -> Option<kludgine::app::winit::window::CursorIcon> {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
|
||||||
|
let hovering = state.test_point(location, true, context);
|
||||||
|
if let Some(hovering) = hovering {
|
||||||
|
let should_remove = state.hovering > Some(hovering);
|
||||||
|
state.hovering = Some(hovering);
|
||||||
|
if should_remove {
|
||||||
|
remove_children_after(state, hovering);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.hovering = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unhover(&mut self, _context: &mut EventContext<'_, '_>) {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
state.hovering = None;
|
||||||
|
|
||||||
|
let mut remove_starting_at = None;
|
||||||
|
for (index, overlay) in state.overlays.iter().enumerate() {
|
||||||
|
if overlay.requires_hover {
|
||||||
|
remove_starting_at = Some(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(remove_starting_at) = remove_starting_at {
|
||||||
|
remove_children_after(state, remove_starting_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Default)]
|
#[derive(Debug, Eq, PartialEq, Default)]
|
||||||
struct OverlayState {
|
struct OverlayState {
|
||||||
overlays: OrderedLots<OverlayLayout>,
|
overlays: OrderedLots<OverlayLayout>,
|
||||||
new_overlays: usize,
|
new_overlays: usize,
|
||||||
|
hovering: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_children_after(mut state: DynamicGuard<'_, OverlayState>, remove_starting_at: usize) {
|
||||||
|
let mut removed = Vec::with_capacity(state.overlays.len() - remove_starting_at);
|
||||||
|
while remove_starting_at < state.overlays.len() && !state.overlays.is_empty() {
|
||||||
|
removed.push(state.overlays.pop());
|
||||||
|
state.new_overlays = state.new_overlays.saturating_sub(1);
|
||||||
|
}
|
||||||
|
drop(state);
|
||||||
|
// We delay dropping the removed widgets, as they may contain a
|
||||||
|
// reference to this OverlayLayer.
|
||||||
|
drop(removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayState {
|
impl OverlayState {
|
||||||
|
fn test_point(
|
||||||
|
&self,
|
||||||
|
location: Point<Px>,
|
||||||
|
check_original_relative: bool,
|
||||||
|
context: &mut EventContext<'_, '_>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
for (index, overlay) in self.overlays.iter().enumerate() {
|
||||||
|
if overlay.requires_hover
|
||||||
|
&& !overlay
|
||||||
|
.layout
|
||||||
|
.map_or(false, |check| !check.contains(location))
|
||||||
|
{
|
||||||
|
return Some(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_original_relative
|
||||||
|
&& !self.overlays.is_empty()
|
||||||
|
&& self.point_is_in_root_relative(location, context)
|
||||||
|
{
|
||||||
|
Some(0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn point_is_in_root_relative(
|
||||||
|
&self,
|
||||||
|
location: Point<Px>,
|
||||||
|
context: &mut EventContext<'_, '_>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(relative_to) = self
|
||||||
|
.overlays
|
||||||
|
.get_by_index(0)
|
||||||
|
.and_then(|overlay| overlay.relative_to)
|
||||||
|
.and_then(|relative_to| context.widget.for_other(&relative_to))
|
||||||
|
.and_then(|c| c.widget().last_layout())
|
||||||
|
{
|
||||||
|
if !relative_to.contains(location) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn process_new_overlays(&mut self, context: &mut EventContext<'_, '_>) {
|
fn process_new_overlays(&mut self, context: &mut EventContext<'_, '_>) {
|
||||||
while self.new_overlays > 0 {
|
while self.new_overlays > 0 {
|
||||||
let new_index = self.overlays.len() - self.new_overlays;
|
let new_index = self.overlays.len() - self.new_overlays;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue