Button animations, hover fixes, ComponentType

This commit is contained in:
Jonathan Johnson 2023-11-01 20:11:05 -07:00
parent 6b8e5f886b
commit 64f46a46e2
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
15 changed files with 300 additions and 83 deletions

View file

@ -7,7 +7,7 @@ use gooey::{widgets, Run};
fn main() -> gooey::Result {
let counter = Dynamic::new(0i32);
let label = counter.map_each(ToString::to_string);
Scroll::new(Stack::rows(widgets![
Scroll::new(Stack::columns(widgets![
Label::new(label),
Button::new("+").on_click(counter.with_clone(|counter| {
move |_| {

View file

@ -9,6 +9,7 @@ use std::time::{Duration, Instant};
use alot::{LotId, Lots};
use kempt::Set;
use kludgine::Color;
use crate::value::Dynamic;
@ -337,6 +338,25 @@ where
}
}
impl IntoAnimate for Duration {
type Animate = Self;
fn into_animate(self) -> Self::Animate {
self
}
}
impl Animate for Duration {
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
if let Some(remaining) = self.checked_sub(elapsed) {
*self = remaining;
ControlFlow::Continue(())
} else {
ControlFlow::Break(elapsed - *self)
}
}
}
/// Performs a linear interpolation between two values.
pub trait LinearInterpolate {
/// Interpolate linearly between `self` and `target` using `percent`.
@ -409,6 +429,17 @@ fn integer_lerps() {
test_lerps(&isize::MIN, &isize::MAX, &0);
}
impl LinearInterpolate for Color {
fn lerp(&self, target: &Self, percent: f32) -> Self {
Color::new(
self.red().lerp(&target.red(), percent),
self.green().lerp(&target.green(), percent),
self.blue().lerp(&target.blue(), percent),
self.alpha().lerp(&target.alpha(), percent),
)
}
}
/// An `f32` that is clamped between 0.0 and 1.0 and cannot be NaN or Infinity.
///
/// Because of these restrictions, this type implements `Ord` and `Eq`.

View file

@ -11,7 +11,7 @@ use kludgine::Kludgine;
use crate::graphics::Graphics;
use crate::styles::components::HighlightColor;
use crate::styles::{ComponentDefaultvalue, Styles};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance};
use crate::window::RunningWindow;
@ -126,31 +126,29 @@ impl<'context, 'window> EventContext<'context, 'window> {
}
pub(crate) fn hover(&mut self, location: Point<Px>) {
if let Ok(changes) = self.current_node.tree.hover(Some(self.current_node)) {
for unhovered in changes.unhovered {
let mut context = self.for_other(&unhovered);
unhovered.lock().unhover(&mut context);
}
for hover in changes.hovered {
let mut context = self.for_other(&hover);
hover.lock().hover(location, &mut context);
}
let changes = self.current_node.tree.hover(Some(self.current_node));
for unhovered in changes.unhovered {
let mut context = self.for_other(&unhovered);
unhovered.lock().unhover(&mut context);
}
for hover in changes.hovered {
let mut context = self.for_other(&hover);
hover.lock().hover(location, &mut context);
}
}
pub(crate) fn clear_hover(&mut self) {
if let Ok(changes) = self.current_node.tree.hover(None) {
assert!(changes.hovered.is_empty());
let changes = self.current_node.tree.hover(None);
assert!(changes.hovered.is_empty());
for old_hover in changes.unhovered {
let mut old_hover_context = self.for_other(&old_hover);
old_hover.lock().unhover(&mut old_hover_context);
}
for old_hover in changes.unhovered {
let mut old_hover_context = self.for_other(&old_hover);
old_hover.lock().unhover(&mut old_hover_context);
}
}
pub(crate) fn apply_pending_state(&mut self) {
let active = self.pending_state.active.take();
let active = self.pending_state.active.clone();
if self.current_node.tree.active_widget() != active.as_ref().map(|active| active.id) {
let new = match self.current_node.tree.activate(active.as_ref()) {
Ok(old) => {
@ -164,12 +162,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
};
if new {
if let Some(active) = active {
active.lock().activate(self);
active.lock().activate(&mut self.for_other(&active));
}
}
}
let focus = self.pending_state.focus.take();
let focus = self.pending_state.focus.clone();
if self.current_node.tree.focused_widget() != focus.as_ref().map(|focus| focus.id) {
let new = match self.current_node.tree.focus(focus.as_ref()) {
Ok(old) => {
@ -183,7 +181,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
};
if new {
if let Some(focus) = focus {
focus.lock().focus(self);
focus.lock().focus(&mut self.for_other(&focus));
}
}
}
@ -296,7 +294,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
/// Renders the default focus ring for this widget.
pub fn draw_focus_ring(&mut self) {
self.draw_focus_ring_using(&self.query_style(&[&HighlightColor]));
self.draw_focus_ring_using(&self.query_styles(&[&HighlightColor]));
}
/// Invokes [`Widget::measure()`](crate::widget::Widget::measure) on this
@ -559,7 +557,23 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// widget is provided as a convenient way to attach styles into the widget
/// hierarchy.
#[must_use]
pub fn query_style(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
self.current_node
.tree
.query_styles(self.current_node, query)
}
/// Queries the widget hierarchy for a single style component.
///
/// This function traverses up the widget hierarchy looking for the
/// component being requested. If a matching component is found, it will be
/// returned. Otherwise, the default value will be returned.
#[must_use]
pub fn query_style<Component: ComponentDefinition>(
&self,
query: &Component,
) -> Component::ComponentType {
self.current_node.tree.query_style(self.current_node, query)
}
}

View file

@ -208,7 +208,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// used.
///
/// `origin` allows controlling how the text will be drawn relative to the
/// coordinate provided in [`render()`](crate::PreparedGraphic::render).
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
pub fn draw_text_buffer<Unit>(
&mut self,
buffer: &cosmic_text::Buffer,
@ -244,7 +244,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// used.
///
/// `origin` allows controlling how the text will be drawn relative to the
/// coordinate provided in [`render()`](crate::PreparedGraphic::render).
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
pub fn draw_measured_text<Unit>(
&mut self,
text: &MeasuredText<Unit>,

View file

@ -71,7 +71,9 @@ impl Styles {
self.0
.get(&name.group)
.and_then(|group| group.get(&name.name))
.and_then(|component| component.clone().try_into().ok())
.and_then(|component| {
<Named::ComponentType>::try_from_component(component.clone()).ok()
})
.unwrap_or_else(|| component.default_value())
}
}
@ -143,7 +145,7 @@ pub enum Component {
/// A percentage between 0.0 and 1.0.
Percent(f32),
/// A custom component type.
Boxed(CustomComponent),
Custom(CustomComponent),
}
impl From<Color> for Component {
@ -223,6 +225,12 @@ pub enum Dimension {
Lp(Lp),
}
impl Default for Dimension {
fn default() -> Self {
Self::Px(Px(0))
}
}
impl From<Px> for Dimension {
fn from(value: Px) -> Self {
Self::Px(value)
@ -286,6 +294,19 @@ impl CustomComponent {
}
}
impl ComponentType for CustomComponent {
fn into_component(self) -> Component {
Component::Custom(self)
}
fn try_from_component(component: Component) -> Result<Self, Component> {
match component {
Component::Custom(custom) => Ok(custom),
other => Err(other),
}
}
}
trait AnyComponent: Send + Sync + RefUnwindSafe + UnwindSafe + Debug {
fn as_any(&self) -> &dyn Any;
}
@ -378,12 +399,34 @@ pub trait NamedComponent {
/// Rust type.
pub trait ComponentDefinition: NamedComponent {
/// The type that will be contained in the [`Component`].
type ComponentType: Into<Component> + TryFrom<Component, Error = Component>;
type ComponentType: ComponentType;
/// Returns the default value to use for this component.
fn default_value(&self) -> Self::ComponentType;
}
/// A type that can be converted to and from [`Component`].
pub trait ComponentType: Sized {
/// Returns this type, wrapped in a [`Component`].
fn into_component(self) -> Component;
/// Attempts to extract this type from `component`. If `component` does not
/// contain this type, `Err(component)` is returned.
fn try_from_component(component: Component) -> Result<Self, Component>;
}
impl<T> ComponentType for T
where
T: Into<Component> + TryFrom<Component, Error = Component>,
{
fn into_component(self) -> Component {
self.into()
}
fn try_from_component(component: Component) -> Result<Self, Component> {
Self::try_from(component)
}
}
/// A type that represents a named component with a default value.
pub trait ComponentDefaultvalue: NamedComponent {
/// Returns the default value for this component.
@ -395,7 +438,7 @@ where
T: ComponentDefinition,
{
fn default_component_value(&self) -> Component {
self.default_value().into()
self.default_value().into_component()
}
}

View file

@ -77,3 +77,25 @@ impl ComponentDefinition for HighlightColor {
Color::AQUA
}
}
/// Intrinsic, uniform padding for a widget.
///
/// This component is opt-in and does not automatically work for all widgets. To
/// apply arbitrary, non-uniform padding around another widget, use a
/// [`Cell`](crate::widgets::Cell).
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct IntrinsicPadding;
impl NamedComponent for IntrinsicPadding {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Owned(ComponentName::named::<Global>("padding"))
}
}
impl ComponentDefinition for IntrinsicPadding {
type ComponentType = Dimension;
fn default_value(&self) -> Dimension {
Dimension::Lp(Lp::points(5))
}
}

View file

@ -6,7 +6,7 @@ use alot::{LotId, Lots};
use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect};
use crate::styles::{ComponentDefaultvalue, Styles};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
use crate::widget::{ManagedWidget, WidgetInstance};
#[derive(Clone, Default)]
@ -61,31 +61,26 @@ impl Tree {
data.render_order.clear();
}
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> Result<HoverResults, ()> {
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let mut hovered = new_hover
let hovered = new_hover
.map(|new_hover| data.widget_hierarchy(new_hover.id, self))
.unwrap_or_default();
match data.update_tracked_widget(new_hover, self, |data| &mut data.hover)? {
Some(old_hover) => {
let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.hover) {
Ok(Some(old_hover)) => {
let mut old_hovered = data.widget_hierarchy(old_hover.id, self);
// For any widgets that were shared, remove them, as they don't
// need to have their events fired again.
while !old_hovered.is_empty() && old_hovered.get(0) == hovered.get(0) {
let mut new_index = 0;
while !old_hovered.is_empty() && old_hovered.get(0) == hovered.get(new_index) {
old_hovered.remove(0);
hovered.remove(0);
new_index += 1;
}
Ok(HoverResults {
unhovered: old_hovered,
hovered,
})
old_hovered
}
None => Ok(HoverResults {
unhovered: Vec::new(),
hovered,
}),
}
_ => Vec::new(),
};
HoverResults { unhovered, hovered }
}
pub fn focus(&self, new_focus: Option<&ManagedWidget>) -> Result<Option<ManagedWidget>, ()> {
@ -167,7 +162,7 @@ impl Tree {
data.nodes[id.0].styles = Some(styles);
}
pub fn query_style(
pub fn query_styles(
&self,
perspective: &ManagedWidget,
query: &[&dyn ComponentDefaultvalue],
@ -175,7 +170,18 @@ impl Tree {
self.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.query_style(perspective.id, query)
.query_styles(perspective.id, query)
}
pub fn query_style<Component: ComponentDefinition>(
&self,
perspective: &ManagedWidget,
component: &Component,
) -> Component::ComponentType {
self.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.query_style(perspective.id, component)
}
}
@ -255,7 +261,7 @@ impl TreeData {
}
}
fn query_style(
fn query_styles(
&self,
mut perspective: WidgetId,
query: &[&dyn ComponentDefaultvalue],
@ -279,6 +285,30 @@ impl TreeData {
}
resolved
}
fn query_style<Component: ComponentDefinition>(
&self,
mut perspective: WidgetId,
query: &Component,
) -> Component::ComponentType {
let name = query.name();
loop {
let node = &self.nodes[perspective.0];
if let Some(styles) = &node.styles {
if let Some(component) = styles.get(&name) {
let Ok(value) =
<Component::ComponentType>::try_from_component(component.clone())
else {
break;
};
return value;
}
}
let Some(parent) = node.parent else { break };
perspective = parent;
}
query.default_value()
}
}
pub struct Node {

View file

@ -433,6 +433,15 @@ impl<T> Value<T> {
}
}
impl<T> Default for Value<T>
where
T: Default,
{
fn default() -> Self {
Self::Constant(T::default())
}
}
/// A type that can be converted into a [`Value`].
pub trait IntoValue<T> {
/// Returns this type as a [`Value`].

View file

@ -1,10 +1,10 @@
//! Built-in [`Widget`](crate::widget::Widget) implementations.
mod button;
pub mod button;
mod canvas;
mod input;
mod label;
mod scroll;
pub mod scroll;
pub mod stack;
mod style;
mod tilemap;

View file

@ -1,19 +1,22 @@
//! A clickable, labeled button
use std::borrow::Cow;
use std::panic::UnwindSafe;
use std::time::Duration;
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
use kludgine::app::winit::keyboard::KeyCode;
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoUnsigned, Point, Rect, Size};
use kludgine::figures::{IntoUnsigned, Point, Rect, ScreenScale, Size};
use kludgine::shapes::Shape;
use kludgine::text::Text;
use kludgine::Color;
use crate::context::{EventContext, GraphicsContext};
use crate::animation::{Animation, AnimationHandle, Spawn};
use crate::context::{EventContext, GraphicsContext, WidgetContext};
use crate::names::Name;
use crate::styles::components::{HighlightColor, TextColor};
use crate::styles::components::{HighlightColor, IntrinsicPadding, TextColor};
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
use crate::value::{IntoValue, Value};
use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
/// A clickable button.
@ -24,6 +27,8 @@ pub struct Button {
/// The callback that is invoked when the button is clicked.
pub on_click: Option<Callback<()>>,
buttons_pressed: usize,
background_color: Option<Dynamic<Color>>,
background_color_animation: AnimationHandle,
}
impl Button {
@ -33,6 +38,8 @@ impl Button {
label: label.into_value(),
on_click: None,
buttons_pressed: 0,
background_color: None,
background_color_animation: AnimationHandle::default(),
}
}
@ -53,6 +60,50 @@ impl Button {
on_click.invoke(());
}
}
fn update_background_color(&mut self, context: &WidgetContext<'_, '_>, immediate: bool) {
let styles = context.query_styles(&[
&ButtonActiveBackground,
&ButtonBackground,
&ButtonHoverBackground,
]);
let background_color = if context.active() {
styles.get_or_default(&ButtonActiveBackground)
} else if context.hovered() {
styles.get_or_default(&ButtonHoverBackground)
} else {
styles.get_or_default(&ButtonBackground)
};
match (immediate, &self.background_color) {
(false, Some(dynamic)) => {
self.background_color_animation = Animation::linear(
dynamic.clone(),
background_color,
Duration::from_millis(150),
)
.spawn();
}
(true, Some(dynamic)) => {
dynamic.set(background_color);
self.background_color_animation.clear();
}
(_, None) => {
let dynamic = Dynamic::new(background_color);
self.background_color = Some(dynamic);
}
}
}
fn current_background_color(&mut self, context: &WidgetContext<'_, '_>) -> Color {
if self.background_color.is_none() {
self.update_background_color(context, false);
}
let background_color = self.background_color.as_ref().expect("always initialized");
context.redraw_when_changed(background_color);
background_color.get()
}
}
impl Widget for Button {
@ -61,7 +112,7 @@ impl Widget for Button {
let center = Point::from(size) / 2;
self.label.redraw_when_changed(context);
let styles = context.query_style(&[
let styles = context.query_styles(&[
&TextColor,
&HighlightColor,
&ButtonActiveBackground,
@ -71,13 +122,7 @@ impl Widget for Button {
let visible_rect = Rect::from(size - (Px(1), Px(1)));
let background = if context.active() {
styles.get_or_default(&ButtonActiveBackground)
} else if context.hovered() {
styles.get_or_default(&ButtonHoverBackground)
} else {
styles.get_or_default(&ButtonBackground)
};
let background = self.current_background_color(context);
let background = Shape::filled_rect(visible_rect, background);
context
.graphics
@ -173,6 +218,10 @@ impl Widget for Button {
available_space: Size<crate::ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let padding = context
.query_style(&IntrinsicPadding)
.into_px(context.graphics.scale())
.into_unsigned();
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
self.label.map(|label| {
let measured = context
@ -180,7 +229,8 @@ impl Widget for Button {
.measure_text::<Px>(Text::from(label).wrap_at(width));
let mut size = measured.size.into_unsigned();
size.height = size.height.max(measured.line_height.into_unsigned());
size.width += padding * 2;
size.height = size.height.max(measured.line_height.into_unsigned()) + padding * 2;
size
})
}
@ -210,11 +260,11 @@ impl Widget for Button {
}
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {
context.set_needs_redraw();
self.update_background_color(context, false);
}
fn hover(&mut self, _location: Point<Px>, context: &mut EventContext<'_, '_>) {
context.set_needs_redraw();
self.update_background_color(context, false);
}
fn focus(&mut self, context: &mut EventContext<'_, '_>) {
@ -226,11 +276,11 @@ impl Widget for Button {
}
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
context.set_needs_redraw();
self.update_background_color(context, true);
}
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {
context.set_needs_redraw();
self.update_background_color(context, false);
}
}
@ -240,6 +290,7 @@ impl ComponentGroup for Button {
}
}
/// The background color of the button.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct ButtonBackground;
@ -257,6 +308,7 @@ impl ComponentDefinition for ButtonBackground {
}
}
/// The background color of the button when it is active (depressed).
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct ButtonActiveBackground;
@ -274,6 +326,8 @@ impl ComponentDefinition for ButtonActiveBackground {
}
}
/// The background color of the button when the mouse cursor is hovering over
/// it.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct ButtonHoverBackground;

View file

@ -80,7 +80,7 @@ impl Input {
}
fn styles(context: &WidgetContext<'_, '_>) -> Styles {
context.query_style(&[&TextColor, &TextSize, &LineHeight])
context.query_styles(&[&TextColor, &TextSize, &LineHeight])
}
}
@ -105,7 +105,7 @@ impl Widget for Input {
context: &mut EventContext<'_, '_>,
) -> EventHandling {
context.focus();
let styles = context.query_style(&[&TextColor]);
let styles = context.query_styles(&[&TextColor]);
self.editor_mut(context.kludgine, &styles).action(
context.kludgine.font_system(),
Action::Click {
@ -124,7 +124,7 @@ impl Widget for Input {
_button: kludgine::app::winit::event::MouseButton,
context: &mut EventContext<'_, '_>,
) {
let styles = context.query_style(&[&TextColor]);
let styles = context.query_styles(&[&TextColor]);
self.editor_mut(context.kludgine, &styles).action(
context.kludgine.font_system(),
Action::Drag {
@ -141,7 +141,7 @@ impl Widget for Input {
self.cursor_state.update(context.elapsed());
let cursor_state = self.cursor_state;
let size = context.graphics.size();
let styles = context.query_style(&[&TextColor, &HighlightColor]);
let styles = context.query_styles(&[&TextColor, &HighlightColor]);
let highlight = styles.get_or_default(&HighlightColor);
let editor = self.editor_mut(&mut context.graphics, &styles);
let cursor = editor.cursor();
@ -293,7 +293,7 @@ impl Widget for Input {
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
) -> kludgine::figures::Size<kludgine::figures::units::UPx> {
let styles = context.query_style(&[&TextColor]);
let styles = context.query_styles(&[&TextColor]);
let editor = self.editor_mut(&mut context.graphics, &styles);
let buffer = editor.buffer_mut();
buffer.set_size(
@ -319,7 +319,7 @@ impl Widget for Input {
return IGNORED;
}
let styles = context.query_style(&[&TextColor]);
let styles = context.query_styles(&[&TextColor]);
let editor = self.editor_mut(context.kludgine, &styles);
println!(

View file

@ -1,9 +1,9 @@
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{Point, Size};
use kludgine::figures::{IntoUnsigned, Point, ScreenScale, Size};
use kludgine::text::{MeasuredText, Text, TextOrigin};
use crate::context::GraphicsContext;
use crate::styles::components::TextColor;
use crate::styles::components::{IntrinsicPadding, TextColor};
use crate::value::{IntoValue, Value};
use crate::widget::Widget;
@ -31,7 +31,7 @@ impl Widget for Label {
let size = context.graphics.region().size;
let center = Point::from(size) / 2;
let styles = context.query_style(&[&TextColor]);
let styles = context.query_styles(&[&TextColor]);
if let Some(measured) = &self.prepared_text {
context
@ -56,12 +56,17 @@ impl Widget for Label {
available_space: Size<crate::ConstraintLimit>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> {
let padding = context
.query_style(&IntrinsicPadding)
.into_px(context.graphics.scale())
.into_unsigned();
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
self.text.map(|contents| {
let measured = context
.graphics
.measure_text(Text::from(contents).wrap_at(width));
let size = measured.size.try_cast().unwrap_or_default();
let mut size = measured.size.try_cast().unwrap_or_default();
size += padding * 2;
self.prepared_text = Some(measured);
size
})

View file

@ -1,3 +1,4 @@
//! A container that scrolls its contents on a virtual surface.
use std::borrow::Cow;
use std::time::Duration;
@ -10,7 +11,7 @@ use kludgine::figures::{
use kludgine::shapes::Shape;
use kludgine::Color;
use crate::animation::{Animation, AnimationHandle, Spawn, ZeroToOne};
use crate::animation::{Animation, AnimationHandle, IntoAnimate, Spawn, ZeroToOne};
use crate::context::{AsEventContext, EventContext};
use crate::styles::{
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
@ -82,6 +83,12 @@ impl Widget for Scroll {
ZeroToOne::ONE,
Duration::from_millis(300),
)
.chain(Duration::from_secs(1))
.chain(Animation::linear(
self.scrollbar_opacity.clone(),
ZeroToOne::ZERO,
Duration::from_millis(300),
))
.spawn();
}
@ -101,7 +108,7 @@ impl Widget for Scroll {
return;
};
let visible_bottom_right = visible_rect.into_signed().extent();
let styles = context.query_style(&[&ScrollBarThickness]);
let styles = context.query_styles(&[&ScrollBarThickness]);
let bar_width = styles
.get_or_default(&ScrollBarThickness)
.into_px(context.graphics.scale());
@ -219,13 +226,14 @@ fn scrollbar_region(scroll: Px, content_size: Px, control_size: Px) -> Scrollbar
}
}
/// The thickness that scrollbars are drawn with.
pub struct ScrollBarThickness;
impl ComponentDefinition for ScrollBarThickness {
type ComponentType = Dimension;
fn default_value(&self) -> Self::ComponentType {
Dimension::Lp(Lp::points(9))
Dimension::Lp(Lp::points(7))
}
}

View file

@ -27,7 +27,7 @@ impl<Layers> TileMap<Layers> {
fn construct(layers: Value<Layers>) -> Self {
Self {
layers,
focus: Value::Constant(TileMapFocus::default()),
focus: Value::default(),
zoom: 1.,
tick: None,
}

View file

@ -366,7 +366,8 @@ where
) {
match state {
ElementState::Pressed => {
WidgetContext::new(&self.root, &mut window).clear_focus();
EventContext::new(WidgetContext::new(&self.root, &mut window), kludgine)
.clear_focus();
if let (ElementState::Pressed, Some(location), Some(hovered)) =
(state, &self.mouse_state.location, &self.mouse_state.widget)