Added hover support to OverlayLayer

This commit is contained in:
Jonathan Johnson 2023-12-07 09:51:07 -08:00
parent 0d34924ddf
commit 2fe08fc9e9
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
6 changed files with 192 additions and 56 deletions

22
Cargo.lock generated
View file

@ -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"

View file

@ -48,6 +48,7 @@ opt-level = 2
[dev-dependencies]
pollster = "0.3.0"
rand = "0.8.5"
[profile.release]
debug = true

View file

@ -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();
})
}

View file

@ -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;

View file

@ -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()));
}

View file

@ -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;