mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 22:41:10 +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",
|
||||
"palette",
|
||||
"pollster",
|
||||
"rand",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"unicode-segmentation",
|
||||
|
|
@ -1711,6 +1712,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "presser"
|
||||
version = "0.3.1"
|
||||
|
|
@ -1809,6 +1816,18 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
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",
|
||||
]
|
||||
|
||||
|
|
@ -1817,6 +1836,9 @@ name = "rand_core"
|
|||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ opt-level = 2
|
|||
|
||||
[dev-dependencies]
|
||||
pollster = "0.3.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
|
|||
|
|
@ -1,65 +1,60 @@
|
|||
use std::panic::UnwindSafe;
|
||||
|
||||
use gooey::widget::{MakeWidget, MakeWidgetWithId, WidgetTag};
|
||||
use gooey::widgets::layers::OverlayLayer;
|
||||
use gooey::widgets::layers::{OverlayBuilder, OverlayLayer};
|
||||
use gooey::Run;
|
||||
use kludgine::Color;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let overlay = OverlayLayer::default();
|
||||
|
||||
test_widget(&overlay)
|
||||
test_widget(&overlay, true)
|
||||
.centered()
|
||||
.and(overlay)
|
||||
.into_layers()
|
||||
.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 right = "Right".into_button().on_click({
|
||||
let overlay = overlay.clone();
|
||||
move |()| {
|
||||
overlay
|
||||
.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();
|
||||
}
|
||||
});
|
||||
let right = show_overlay_button("Right", overlay, move |overlay| overlay.right_of(my_id));
|
||||
let left = show_overlay_button("Left", overlay, move |overlay| overlay.left_of(my_id));
|
||||
let up = show_overlay_button("Up", overlay, move |overlay| overlay.above(my_id));
|
||||
let down = show_overlay_button("Down", overlay, move |overlay| overlay.below(my_id));
|
||||
|
||||
up.centered()
|
||||
let mut buttons = up
|
||||
.centered()
|
||||
.and(left.and(right).into_columns())
|
||||
.and(down.centered())
|
||||
.into_rows()
|
||||
.contain()
|
||||
.pad()
|
||||
.make_with_id(my_tag)
|
||||
.contain();
|
||||
|
||||
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;
|
||||
for hover in changes.hovered.into_iter().rev() {
|
||||
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() {
|
||||
cursor = widget_cursor;
|
||||
|
|
@ -330,7 +336,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
|||
let relative = location - widget_layout.origin;
|
||||
|
||||
if widget_context.hit_test(relative) {
|
||||
widget_context.hover(relative);
|
||||
widget_context.hover(location);
|
||||
drop(widget_context);
|
||||
self.cursor.widget = Some(widget.id());
|
||||
break;
|
||||
|
|
|
|||
10
src/value.rs
10
src/value.rs
|
|
@ -536,6 +536,7 @@ impl<T> Dynamic<T> {
|
|||
DynamicGuard {
|
||||
guard: self.0.state().expect("deadlocked"),
|
||||
accessed_mut: false,
|
||||
prevent_notifications: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1100,6 +1101,7 @@ impl<T> DerefMut for GenerationalValue<T> {
|
|||
pub struct DynamicGuard<'a, T> {
|
||||
guard: DynamicMutexGuard<'a, T>,
|
||||
accessed_mut: bool,
|
||||
prevent_notifications: bool,
|
||||
}
|
||||
|
||||
impl<T> DynamicGuard<'_, T> {
|
||||
|
|
@ -1111,6 +1113,12 @@ impl<T> DynamicGuard<'_, T> {
|
|||
pub fn generation(&self) -> 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> {
|
||||
|
|
@ -1130,7 +1138,7 @@ impl<'a, T> DerefMut for DynamicGuard<'a, T> {
|
|||
|
||||
impl<T> Drop for DynamicGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.accessed_mut {
|
||||
if self.accessed_mut && !self.prevent_notifications {
|
||||
let mut callbacks = Some(self.guard.note_changed());
|
||||
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 crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::value::{Dynamic, Generation, IntoValue, Value};
|
||||
use crate::value::{Dynamic, DynamicGuard, Generation, IntoValue, Value};
|
||||
use crate::widget::{
|
||||
Children, MakeWidget, ManagedWidget, OnceCallback, Widget, WidgetId, WidgetRef,
|
||||
};
|
||||
|
|
@ -194,8 +194,7 @@ impl OverlayLayer {
|
|||
|
||||
impl Widget for OverlayLayer {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let mut guard = self.state.lock();
|
||||
let state = &mut *guard;
|
||||
let state = self.state.lock();
|
||||
|
||||
for child in &state.overlays {
|
||||
let WidgetRef::Mounted(mounted) = &child.widget else {
|
||||
|
|
@ -211,8 +210,8 @@ impl Widget for OverlayLayer {
|
|||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let mut guard = self.state.lock();
|
||||
let state = &mut *guard;
|
||||
let mut state = self.state.lock();
|
||||
state.prevent_notifications();
|
||||
|
||||
let available_space = available_space.map(ConstraintLimit::max);
|
||||
|
||||
|
|
@ -237,7 +236,7 @@ impl Widget for OverlayLayer {
|
|||
context.set_child_layout(&widget, layout);
|
||||
}
|
||||
|
||||
drop(guard);
|
||||
drop(state);
|
||||
|
||||
// Now that we're done mutating state, we can register for invalidation
|
||||
// tracking.
|
||||
|
|
@ -249,15 +248,120 @@ impl Widget for OverlayLayer {
|
|||
// when shown.
|
||||
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)]
|
||||
struct OverlayState {
|
||||
overlays: OrderedLots<OverlayLayout>,
|
||||
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 {
|
||||
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<'_, '_>) {
|
||||
while self.new_overlays > 0 {
|
||||
let new_index = self.overlays.len() - self.new_overlays;
|
||||
|
|
|
|||
Loading…
Reference in a new issue