Enable/disable is now handled for all widgets

Closes #66
This commit is contained in:
Jonathan Johnson 2023-11-15 10:23:42 -08:00
parent 5965f19d27
commit 534f676ef0
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
8 changed files with 105 additions and 30 deletions

View file

@ -55,12 +55,12 @@ fn main() -> gooey::Result {
.and(
"Log In"
.into_button()
.enabled(valid)
.on_click(move |_| {
println!("Welcome, {}", username.get());
exit(0);
})
.make_with_id(login_tag)
.with_enabled(valid)
.into_default()
.with_next_focus(cancel_id),
)

View file

@ -36,12 +36,12 @@ fn main() -> gooey::Result {
.and(
"Log In"
.into_button()
.enabled(valid)
.on_click(move |_| {
println!("Welcome, {}", username.get());
exit(0);
})
.into_default(),
.into_default()
.with_enabled(valid),
)
.into_columns();

View file

@ -191,9 +191,9 @@ fn square(row: usize, column: usize, game: &Dynamic<GameState>) -> impl MakeWidg
label
.clone()
.into_button()
.enabled(enabled)
.kind(ButtonKind::Outline)
.on_click(move |_| game.lock().play(row, column))
.with_enabled(enabled)
.pad()
.expand()
}

View file

@ -229,11 +229,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
focus_changes += 1;
self.pending_state.focus = focus.and_then(|mut focus| loop {
if focus
.lock()
.as_widget()
.accept_focus(&mut self.for_other(&focus))
{
let mut focus_context = self.for_other(&focus);
let accept_focus = focus_context.enabled()
&& focus.lock().as_widget().accept_focus(&mut focus_context);
drop(focus_context);
if accept_focus {
break Some(focus);
} else if let Some(next_focus) =
focus.explicit_focus_target(self.pending_state.focus_is_advancing)
@ -707,6 +708,7 @@ pub struct WidgetContext<'context, 'window> {
pending_state: PendingState<'context>,
theme_mode: ThemeMode,
effective_styles: Styles,
enabled: bool,
}
impl<'context, 'window> WidgetContext<'context, 'window> {
@ -717,6 +719,10 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
window: &'context mut RunningWindow<'window>,
theme_mode: ThemeMode,
) -> Self {
let enabled = current_node.enabled(&WindowHandle {
kludgine: window.handle(),
redraw_status: redraw_status.clone(),
});
Self {
pending_state: PendingState::Owned(PendingWidgetState {
focus: current_node
@ -730,6 +736,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
focus_is_advancing: false,
}),
effective_styles: current_node.effective_styles(),
enabled,
current_node,
redraw_status,
theme: Cow::Borrowed(theme),
@ -748,6 +755,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
pending_state: self.pending_state.borrowed(),
theme_mode: self.theme_mode,
effective_styles: self.effective_styles.clone(),
enabled: self.enabled,
}
}
@ -774,6 +782,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
};
WidgetContext {
effective_styles,
enabled: current_node.enabled(&self.handle()),
current_node,
redraw_status: self.redraw_status,
window: &mut *self.window,
@ -784,6 +793,12 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
})
}
/// Returns true if this widget is enabled.
#[must_use]
pub const fn enabled(&self) -> bool {
self.enabled
}
pub(crate) fn parent(&self) -> Option<ManagedWidget> {
self.current_node.parent()
}
@ -1005,6 +1020,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
}
}
#[derive(Clone)]
pub(crate) struct WindowHandle {
kludgine: kludgine::app::WindowHandle<WindowCommand>,
redraw_status: InvalidationStatus,

View file

@ -6,6 +6,7 @@ use alot::{LotId, Lots};
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, Rect, Size};
use crate::context::WindowHandle;
use crate::styles::{Styles, ThemePair, VisualOrder};
use crate::utils::IgnorePoison;
use crate::value::Value;
@ -44,6 +45,7 @@ impl Tree {
effective_styles,
theme: None,
theme_mode: None,
invalidation: 0,
});
data.nodes_by_id.insert(id, node_id);
if widget.is_default() {
@ -275,6 +277,25 @@ impl Tree {
data.widget_from_node(id, self)
}
pub(crate) fn is_enabled(&self, mut id: LotId, context: &WindowHandle) -> bool {
let data = self.data.lock().ignore_poison();
loop {
let Some(node) = data.nodes.get(id) else {
return false;
};
if !node.widget.enabled(context) {
return false;
}
let Some(parent) = node.parent else { break };
id = parent;
}
true
}
pub(crate) fn active_widget(&self) -> Option<LotId> {
self.data.lock().ignore_poison().active
}
@ -481,6 +502,7 @@ impl TreeData {
let mut node = &mut self.nodes[id];
while node.layout.is_some() {
node.layout = None;
node.invalidation += 1;
node.last_layout_query = None;
let (true, Some(parent)) = (include_hierarchy, node.parent) else {
@ -552,6 +574,7 @@ struct Node {
children: Vec<LotId>,
parent: Option<LotId>,
layout: Option<Rect<Px>>,
invalidation: u64,
last_layout_query: Option<CachedLayoutQuery>,
associated_styles: Option<Value<Styles>>,
effective_styles: Styles,

View file

@ -16,7 +16,9 @@ use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
use kludgine::Color;
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::context::{
AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext, WindowHandle,
};
use crate::styles::{
ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, NamedComponent, Styles,
ThemePair, VisualOrder,
@ -580,6 +582,19 @@ pub trait MakeWidget: Sized {
self.make_widget().with_next_focus(next_focus)
}
/// Sets this widget to be enabled/disabled based on `enabled` and returns
/// self.
///
/// If this widget is disabled, all children widgets will also be disabled.
///
/// # Panics
///
/// This function can only be called when one instance of the widget exists.
/// If any clones exist, a panic will occur.
fn with_enabled(self, enabled: impl IntoValue<bool>) -> WidgetInstance {
self.make_widget().with_enabled(enabled)
}
/// Sets this widget as a "default" widget.
///
/// Default widgets are automatically activated when the user signals they
@ -846,6 +861,7 @@ struct WidgetInstanceData {
default: bool,
cancel: bool,
next_focus: Value<Option<WidgetId>>,
enabled: Value<bool>,
widget: Box<Mutex<dyn AnyWidget>>,
}
@ -863,6 +879,7 @@ impl WidgetInstance {
default: false,
cancel: false,
widget: Box::new(Mutex::new(widget)),
enabled: Value::Constant(true),
}),
}
}
@ -901,6 +918,23 @@ impl WidgetInstance {
self
}
/// Sets this widget to be enabled/disabled based on `enabled` and returns
/// self.
///
/// If this widget is disabled, all children widgets will also be disabled.
///
/// # Panics
///
/// This function can only be called when one instance of the widget exists.
/// If any clones exist, a panic will occur.
#[must_use]
pub fn with_enabled(mut self, enabled: impl IntoValue<bool>) -> WidgetInstance {
let data = Arc::get_mut(&mut self.data)
.expect("with_enabled can only be called on newly created widget instances");
data.enabled = enabled.into_value();
self
}
/// Sets this widget as a "default" widget.
///
/// Default widgets are automatically activated when the user signals they
@ -982,6 +1016,13 @@ impl WidgetInstance {
pub fn is_escape(&self) -> bool {
self.data.cancel
}
pub(crate) fn enabled(&self, context: &WindowHandle) -> bool {
if let Value::Dynamic(dynamic) = &self.data.enabled {
dynamic.redraw_when_changed(context.clone());
}
self.data.enabled.get()
}
}
impl AsRef<WidgetId> for WidgetInstance {
@ -1141,6 +1182,10 @@ impl ManagedWidget {
self.tree.active_widget() == Some(self.node_id)
}
pub(crate) fn enabled(&self, handle: &WindowHandle) -> bool {
self.tree.is_enabled(self.node_id, handle)
}
/// Returns true if this widget is currently the hovered widget.
#[must_use]
pub fn hovered(&self) -> bool {

View file

@ -26,8 +26,6 @@ pub struct Button {
pub content: WidgetRef,
/// The callback that is invoked when the button is clicked.
pub on_click: Option<Callback<()>>,
/// The enabled state of the button.
pub enabled: Value<bool>,
/// The kind of button to draw.
pub kind: Value<ButtonKind>,
buttons_pressed: usize,
@ -130,7 +128,6 @@ impl Button {
Self {
content: content.widget_ref(),
on_click: None,
enabled: Value::Constant(true),
cached_state: CacheState {
enabled: true,
kind: ButtonKind::default(),
@ -161,23 +158,16 @@ impl Button {
self
}
/// Sets the value to use for the button's enabled status.
#[must_use]
pub fn enabled(mut self, enabled: impl IntoValue<bool>) -> Self {
self.enabled = enabled.into_value();
self
}
fn invoke_on_click(&mut self) {
if self.enabled.get() {
fn invoke_on_click(&mut self, context: &WidgetContext<'_, '_>) {
if context.enabled() {
if let Some(on_click) = self.on_click.as_mut() {
on_click.invoke(());
}
}
}
fn visual_style(&self, context: &WidgetContext<'_, '_>) -> VisualState {
if !self.enabled.get_tracked(context) {
fn visual_style(context: &WidgetContext<'_, '_>) -> VisualState {
if !context.enabled() {
VisualState::Disabled
} else if context.active() {
VisualState::Active
@ -220,7 +210,7 @@ impl Button {
fn determine_stateful_colors(&mut self, context: &mut WidgetContext<'_, '_>) -> ButtonColors {
let kind = self.kind.get_tracked(context);
let visual_state = self.visual_style(context);
let visual_state = Self::visual_style(context);
self.cached_state = CacheState {
enabled: !matches!(visual_state, VisualState::Disabled),
@ -341,7 +331,7 @@ impl VisualState {
impl Widget for Button {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
#![allow(clippy::similar_names)]
let enabled = self.enabled.get_tracked(context);
let enabled = context.enabled();
// TODO This seems ugly. It needs context, so it can't be moved into the
// dynamic system.
@ -403,7 +393,7 @@ impl Widget for Button {
}
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
self.enabled.get() && context.get(&AutoFocusableControls).is_all()
context.get(&AutoFocusableControls).is_all()
}
fn mouse_down(
@ -455,7 +445,7 @@ impl Widget for Button {
{
context.focus();
self.invoke_on_click();
self.invoke_on_click(context);
}
}
}
@ -503,7 +493,7 @@ impl Widget for Button {
let changed = context.activate();
if !changed {
// The widget was already active. This is now a repeated keypress
self.invoke_on_click();
self.invoke_on_click(context);
}
changed
}
@ -538,7 +528,7 @@ impl Widget for Button {
// If we have no buttons pressed, the event should fire on activate not
// on deactivate.
if self.buttons_pressed == 0 {
self.invoke_on_click();
self.invoke_on_click(context);
}
self.update_colors(context, true);
}

View file

@ -1087,6 +1087,7 @@ pub(crate) mod sealed {
pub transparent: bool,
}
#[derive(Clone)]
pub enum WindowCommand {
Redraw,
// RequestClose,