mirror of
https://github.com/danbulant/cushy
synced 2026-07-05 03:00:43 +00:00
Button animations, hover fixes, ComponentType
This commit is contained in:
parent
6b8e5f886b
commit
64f46a46e2
15 changed files with 300 additions and 83 deletions
|
|
@ -7,7 +7,7 @@ use gooey::{widgets, Run};
|
||||||
fn main() -> gooey::Result {
|
fn main() -> gooey::Result {
|
||||||
let counter = Dynamic::new(0i32);
|
let counter = Dynamic::new(0i32);
|
||||||
let label = counter.map_each(ToString::to_string);
|
let label = counter.map_each(ToString::to_string);
|
||||||
Scroll::new(Stack::rows(widgets![
|
Scroll::new(Stack::columns(widgets![
|
||||||
Label::new(label),
|
Label::new(label),
|
||||||
Button::new("+").on_click(counter.with_clone(|counter| {
|
Button::new("+").on_click(counter.with_clone(|counter| {
|
||||||
move |_| {
|
move |_| {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use alot::{LotId, Lots};
|
use alot::{LotId, Lots};
|
||||||
use kempt::Set;
|
use kempt::Set;
|
||||||
|
use kludgine::Color;
|
||||||
|
|
||||||
use crate::value::Dynamic;
|
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.
|
/// Performs a linear interpolation between two values.
|
||||||
pub trait LinearInterpolate {
|
pub trait LinearInterpolate {
|
||||||
/// Interpolate linearly between `self` and `target` using `percent`.
|
/// Interpolate linearly between `self` and `target` using `percent`.
|
||||||
|
|
@ -409,6 +429,17 @@ fn integer_lerps() {
|
||||||
test_lerps(&isize::MIN, &isize::MAX, &0);
|
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.
|
/// 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`.
|
/// Because of these restrictions, this type implements `Ord` and `Eq`.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use kludgine::Kludgine;
|
||||||
|
|
||||||
use crate::graphics::Graphics;
|
use crate::graphics::Graphics;
|
||||||
use crate::styles::components::HighlightColor;
|
use crate::styles::components::HighlightColor;
|
||||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
|
||||||
use crate::value::Dynamic;
|
use crate::value::Dynamic;
|
||||||
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance};
|
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance};
|
||||||
use crate::window::RunningWindow;
|
use crate::window::RunningWindow;
|
||||||
|
|
@ -126,31 +126,29 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn hover(&mut self, location: Point<Px>) {
|
pub(crate) fn hover(&mut self, location: Point<Px>) {
|
||||||
if let Ok(changes) = self.current_node.tree.hover(Some(self.current_node)) {
|
let changes = self.current_node.tree.hover(Some(self.current_node));
|
||||||
for unhovered in changes.unhovered {
|
for unhovered in changes.unhovered {
|
||||||
let mut context = self.for_other(&unhovered);
|
let mut context = self.for_other(&unhovered);
|
||||||
unhovered.lock().unhover(&mut context);
|
unhovered.lock().unhover(&mut context);
|
||||||
}
|
}
|
||||||
for hover in changes.hovered {
|
for hover in changes.hovered {
|
||||||
let mut context = self.for_other(&hover);
|
let mut context = self.for_other(&hover);
|
||||||
hover.lock().hover(location, &mut context);
|
hover.lock().hover(location, &mut context);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear_hover(&mut self) {
|
pub(crate) fn clear_hover(&mut self) {
|
||||||
if let Ok(changes) = self.current_node.tree.hover(None) {
|
let changes = self.current_node.tree.hover(None);
|
||||||
assert!(changes.hovered.is_empty());
|
assert!(changes.hovered.is_empty());
|
||||||
|
|
||||||
for old_hover in changes.unhovered {
|
for old_hover in changes.unhovered {
|
||||||
let mut old_hover_context = self.for_other(&old_hover);
|
let mut old_hover_context = self.for_other(&old_hover);
|
||||||
old_hover.lock().unhover(&mut old_hover_context);
|
old_hover.lock().unhover(&mut old_hover_context);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_pending_state(&mut self) {
|
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) {
|
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()) {
|
let new = match self.current_node.tree.activate(active.as_ref()) {
|
||||||
Ok(old) => {
|
Ok(old) => {
|
||||||
|
|
@ -164,12 +162,12 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
};
|
};
|
||||||
if new {
|
if new {
|
||||||
if let Some(active) = active {
|
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) {
|
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()) {
|
let new = match self.current_node.tree.focus(focus.as_ref()) {
|
||||||
Ok(old) => {
|
Ok(old) => {
|
||||||
|
|
@ -183,7 +181,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
|
||||||
};
|
};
|
||||||
if new {
|
if new {
|
||||||
if let Some(focus) = focus {
|
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.
|
/// Renders the default focus ring for this widget.
|
||||||
pub fn draw_focus_ring(&mut self) {
|
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
|
/// 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
|
/// widget is provided as a convenient way to attach styles into the widget
|
||||||
/// hierarchy.
|
/// hierarchy.
|
||||||
#[must_use]
|
#[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)
|
self.current_node.tree.query_style(self.current_node, query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
||||||
/// used.
|
/// used.
|
||||||
///
|
///
|
||||||
/// `origin` allows controlling how the text will be drawn relative to the
|
/// `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>(
|
pub fn draw_text_buffer<Unit>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &cosmic_text::Buffer,
|
buffer: &cosmic_text::Buffer,
|
||||||
|
|
@ -244,7 +244,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
||||||
/// used.
|
/// used.
|
||||||
///
|
///
|
||||||
/// `origin` allows controlling how the text will be drawn relative to the
|
/// `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>(
|
pub fn draw_measured_text<Unit>(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &MeasuredText<Unit>,
|
text: &MeasuredText<Unit>,
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ impl Styles {
|
||||||
self.0
|
self.0
|
||||||
.get(&name.group)
|
.get(&name.group)
|
||||||
.and_then(|group| group.get(&name.name))
|
.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())
|
.unwrap_or_else(|| component.default_value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +145,7 @@ pub enum Component {
|
||||||
/// A percentage between 0.0 and 1.0.
|
/// A percentage between 0.0 and 1.0.
|
||||||
Percent(f32),
|
Percent(f32),
|
||||||
/// A custom component type.
|
/// A custom component type.
|
||||||
Boxed(CustomComponent),
|
Custom(CustomComponent),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Color> for Component {
|
impl From<Color> for Component {
|
||||||
|
|
@ -223,6 +225,12 @@ pub enum Dimension {
|
||||||
Lp(Lp),
|
Lp(Lp),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Dimension {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Px(Px(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Px> for Dimension {
|
impl From<Px> for Dimension {
|
||||||
fn from(value: Px) -> Self {
|
fn from(value: Px) -> Self {
|
||||||
Self::Px(value)
|
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 {
|
trait AnyComponent: Send + Sync + RefUnwindSafe + UnwindSafe + Debug {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
}
|
}
|
||||||
|
|
@ -378,12 +399,34 @@ pub trait NamedComponent {
|
||||||
/// Rust type.
|
/// Rust type.
|
||||||
pub trait ComponentDefinition: NamedComponent {
|
pub trait ComponentDefinition: NamedComponent {
|
||||||
/// The type that will be contained in the [`Component`].
|
/// 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.
|
/// Returns the default value to use for this component.
|
||||||
fn default_value(&self) -> Self::ComponentType;
|
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.
|
/// A type that represents a named component with a default value.
|
||||||
pub trait ComponentDefaultvalue: NamedComponent {
|
pub trait ComponentDefaultvalue: NamedComponent {
|
||||||
/// Returns the default value for this component.
|
/// Returns the default value for this component.
|
||||||
|
|
@ -395,7 +438,7 @@ where
|
||||||
T: ComponentDefinition,
|
T: ComponentDefinition,
|
||||||
{
|
{
|
||||||
fn default_component_value(&self) -> Component {
|
fn default_component_value(&self) -> Component {
|
||||||
self.default_value().into()
|
self.default_value().into_component()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,25 @@ impl ComponentDefinition for HighlightColor {
|
||||||
Color::AQUA
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
70
src/tree.rs
70
src/tree.rs
|
|
@ -6,7 +6,7 @@ use alot::{LotId, Lots};
|
||||||
use kludgine::figures::units::Px;
|
use kludgine::figures::units::Px;
|
||||||
use kludgine::figures::{Point, Rect};
|
use kludgine::figures::{Point, Rect};
|
||||||
|
|
||||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
|
||||||
use crate::widget::{ManagedWidget, WidgetInstance};
|
use crate::widget::{ManagedWidget, WidgetInstance};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
|
@ -61,31 +61,26 @@ impl Tree {
|
||||||
data.render_order.clear();
|
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 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))
|
.map(|new_hover| data.widget_hierarchy(new_hover.id, self))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match data.update_tracked_widget(new_hover, self, |data| &mut data.hover)? {
|
let unhovered = match data.update_tracked_widget(new_hover, self, |data| &mut data.hover) {
|
||||||
Some(old_hover) => {
|
Ok(Some(old_hover)) => {
|
||||||
let mut old_hovered = data.widget_hierarchy(old_hover.id, self);
|
let mut old_hovered = data.widget_hierarchy(old_hover.id, self);
|
||||||
// For any widgets that were shared, remove them, as they don't
|
// For any widgets that were shared, remove them, as they don't
|
||||||
// need to have their events fired again.
|
// 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);
|
old_hovered.remove(0);
|
||||||
hovered.remove(0);
|
new_index += 1;
|
||||||
}
|
}
|
||||||
|
old_hovered
|
||||||
Ok(HoverResults {
|
|
||||||
unhovered: old_hovered,
|
|
||||||
hovered,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
None => Ok(HoverResults {
|
_ => Vec::new(),
|
||||||
unhovered: Vec::new(),
|
};
|
||||||
hovered,
|
HoverResults { unhovered, hovered }
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus(&self, new_focus: Option<&ManagedWidget>) -> Result<Option<ManagedWidget>, ()> {
|
pub fn focus(&self, new_focus: Option<&ManagedWidget>) -> Result<Option<ManagedWidget>, ()> {
|
||||||
|
|
@ -167,7 +162,7 @@ impl Tree {
|
||||||
data.nodes[id.0].styles = Some(styles);
|
data.nodes[id.0].styles = Some(styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_style(
|
pub fn query_styles(
|
||||||
&self,
|
&self,
|
||||||
perspective: &ManagedWidget,
|
perspective: &ManagedWidget,
|
||||||
query: &[&dyn ComponentDefaultvalue],
|
query: &[&dyn ComponentDefaultvalue],
|
||||||
|
|
@ -175,7 +170,18 @@ impl Tree {
|
||||||
self.data
|
self.data
|
||||||
.lock()
|
.lock()
|
||||||
.map_or_else(PoisonError::into_inner, |g| g)
|
.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,
|
&self,
|
||||||
mut perspective: WidgetId,
|
mut perspective: WidgetId,
|
||||||
query: &[&dyn ComponentDefaultvalue],
|
query: &[&dyn ComponentDefaultvalue],
|
||||||
|
|
@ -279,6 +285,30 @@ impl TreeData {
|
||||||
}
|
}
|
||||||
resolved
|
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 {
|
pub struct Node {
|
||||||
|
|
|
||||||
|
|
@ -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`].
|
/// A type that can be converted into a [`Value`].
|
||||||
pub trait IntoValue<T> {
|
pub trait IntoValue<T> {
|
||||||
/// Returns this type as a [`Value`].
|
/// Returns this type as a [`Value`].
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
//! Built-in [`Widget`](crate::widget::Widget) implementations.
|
//! Built-in [`Widget`](crate::widget::Widget) implementations.
|
||||||
|
|
||||||
mod button;
|
pub mod button;
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod input;
|
mod input;
|
||||||
mod label;
|
mod label;
|
||||||
mod scroll;
|
pub mod scroll;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
mod style;
|
mod style;
|
||||||
mod tilemap;
|
mod tilemap;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
|
//! A clickable, labeled button
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::panic::UnwindSafe;
|
use std::panic::UnwindSafe;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
||||||
use kludgine::app::winit::keyboard::KeyCode;
|
use kludgine::app::winit::keyboard::KeyCode;
|
||||||
use kludgine::figures::units::{Px, UPx};
|
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::shapes::Shape;
|
||||||
use kludgine::text::Text;
|
use kludgine::text::Text;
|
||||||
use kludgine::Color;
|
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::names::Name;
|
||||||
use crate::styles::components::{HighlightColor, TextColor};
|
use crate::styles::components::{HighlightColor, IntrinsicPadding, TextColor};
|
||||||
use crate::styles::{ComponentDefinition, ComponentGroup, ComponentName, NamedComponent};
|
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};
|
use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED};
|
||||||
|
|
||||||
/// A clickable button.
|
/// A clickable button.
|
||||||
|
|
@ -24,6 +27,8 @@ pub struct Button {
|
||||||
/// The callback that is invoked when the button is clicked.
|
/// The callback that is invoked when the button is clicked.
|
||||||
pub on_click: Option<Callback<()>>,
|
pub on_click: Option<Callback<()>>,
|
||||||
buttons_pressed: usize,
|
buttons_pressed: usize,
|
||||||
|
background_color: Option<Dynamic<Color>>,
|
||||||
|
background_color_animation: AnimationHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
|
|
@ -33,6 +38,8 @@ impl Button {
|
||||||
label: label.into_value(),
|
label: label.into_value(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
buttons_pressed: 0,
|
buttons_pressed: 0,
|
||||||
|
background_color: None,
|
||||||
|
background_color_animation: AnimationHandle::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +60,50 @@ impl Button {
|
||||||
on_click.invoke(());
|
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 {
|
impl Widget for Button {
|
||||||
|
|
@ -61,7 +112,7 @@ impl Widget for Button {
|
||||||
let center = Point::from(size) / 2;
|
let center = Point::from(size) / 2;
|
||||||
self.label.redraw_when_changed(context);
|
self.label.redraw_when_changed(context);
|
||||||
|
|
||||||
let styles = context.query_style(&[
|
let styles = context.query_styles(&[
|
||||||
&TextColor,
|
&TextColor,
|
||||||
&HighlightColor,
|
&HighlightColor,
|
||||||
&ButtonActiveBackground,
|
&ButtonActiveBackground,
|
||||||
|
|
@ -71,13 +122,7 @@ impl Widget for Button {
|
||||||
|
|
||||||
let visible_rect = Rect::from(size - (Px(1), Px(1)));
|
let visible_rect = Rect::from(size - (Px(1), Px(1)));
|
||||||
|
|
||||||
let background = if context.active() {
|
let background = self.current_background_color(context);
|
||||||
styles.get_or_default(&ButtonActiveBackground)
|
|
||||||
} else if context.hovered() {
|
|
||||||
styles.get_or_default(&ButtonHoverBackground)
|
|
||||||
} else {
|
|
||||||
styles.get_or_default(&ButtonBackground)
|
|
||||||
};
|
|
||||||
let background = Shape::filled_rect(visible_rect, background);
|
let background = Shape::filled_rect(visible_rect, background);
|
||||||
context
|
context
|
||||||
.graphics
|
.graphics
|
||||||
|
|
@ -173,6 +218,10 @@ impl Widget for Button {
|
||||||
available_space: Size<crate::ConstraintLimit>,
|
available_space: Size<crate::ConstraintLimit>,
|
||||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Size<UPx> {
|
) -> 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);
|
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||||
self.label.map(|label| {
|
self.label.map(|label| {
|
||||||
let measured = context
|
let measured = context
|
||||||
|
|
@ -180,7 +229,8 @@ impl Widget for Button {
|
||||||
.measure_text::<Px>(Text::from(label).wrap_at(width));
|
.measure_text::<Px>(Text::from(label).wrap_at(width));
|
||||||
|
|
||||||
let mut size = measured.size.into_unsigned();
|
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
|
size
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -210,11 +260,11 @@ impl Widget for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unhover(&mut self, context: &mut EventContext<'_, '_>) {
|
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<'_, '_>) {
|
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<'_, '_>) {
|
fn focus(&mut self, context: &mut EventContext<'_, '_>) {
|
||||||
|
|
@ -226,11 +276,11 @@ impl Widget for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
|
fn activate(&mut self, context: &mut EventContext<'_, '_>) {
|
||||||
context.set_needs_redraw();
|
self.update_background_color(context, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {
|
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)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct ButtonBackground;
|
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)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct ButtonActiveBackground;
|
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)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct ButtonHoverBackground;
|
pub struct ButtonHoverBackground;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styles(context: &WidgetContext<'_, '_>) -> Styles {
|
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<'_, '_>,
|
context: &mut EventContext<'_, '_>,
|
||||||
) -> EventHandling {
|
) -> EventHandling {
|
||||||
context.focus();
|
context.focus();
|
||||||
let styles = context.query_style(&[&TextColor]);
|
let styles = context.query_styles(&[&TextColor]);
|
||||||
self.editor_mut(context.kludgine, &styles).action(
|
self.editor_mut(context.kludgine, &styles).action(
|
||||||
context.kludgine.font_system(),
|
context.kludgine.font_system(),
|
||||||
Action::Click {
|
Action::Click {
|
||||||
|
|
@ -124,7 +124,7 @@ impl Widget for Input {
|
||||||
_button: kludgine::app::winit::event::MouseButton,
|
_button: kludgine::app::winit::event::MouseButton,
|
||||||
context: &mut EventContext<'_, '_>,
|
context: &mut EventContext<'_, '_>,
|
||||||
) {
|
) {
|
||||||
let styles = context.query_style(&[&TextColor]);
|
let styles = context.query_styles(&[&TextColor]);
|
||||||
self.editor_mut(context.kludgine, &styles).action(
|
self.editor_mut(context.kludgine, &styles).action(
|
||||||
context.kludgine.font_system(),
|
context.kludgine.font_system(),
|
||||||
Action::Drag {
|
Action::Drag {
|
||||||
|
|
@ -141,7 +141,7 @@ impl Widget for Input {
|
||||||
self.cursor_state.update(context.elapsed());
|
self.cursor_state.update(context.elapsed());
|
||||||
let cursor_state = self.cursor_state;
|
let cursor_state = self.cursor_state;
|
||||||
let size = context.graphics.size();
|
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 highlight = styles.get_or_default(&HighlightColor);
|
||||||
let editor = self.editor_mut(&mut context.graphics, &styles);
|
let editor = self.editor_mut(&mut context.graphics, &styles);
|
||||||
let cursor = editor.cursor();
|
let cursor = editor.cursor();
|
||||||
|
|
@ -293,7 +293,7 @@ impl Widget for Input {
|
||||||
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
|
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
|
||||||
context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
|
context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>,
|
||||||
) -> kludgine::figures::Size<kludgine::figures::units::UPx> {
|
) -> 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 editor = self.editor_mut(&mut context.graphics, &styles);
|
||||||
let buffer = editor.buffer_mut();
|
let buffer = editor.buffer_mut();
|
||||||
buffer.set_size(
|
buffer.set_size(
|
||||||
|
|
@ -319,7 +319,7 @@ impl Widget for Input {
|
||||||
return IGNORED;
|
return IGNORED;
|
||||||
}
|
}
|
||||||
|
|
||||||
let styles = context.query_style(&[&TextColor]);
|
let styles = context.query_styles(&[&TextColor]);
|
||||||
let editor = self.editor_mut(context.kludgine, &styles);
|
let editor = self.editor_mut(context.kludgine, &styles);
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use kludgine::figures::units::{Px, UPx};
|
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 kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||||
|
|
||||||
use crate::context::GraphicsContext;
|
use crate::context::GraphicsContext;
|
||||||
use crate::styles::components::TextColor;
|
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||||
use crate::value::{IntoValue, Value};
|
use crate::value::{IntoValue, Value};
|
||||||
use crate::widget::Widget;
|
use crate::widget::Widget;
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl Widget for Label {
|
||||||
|
|
||||||
let size = context.graphics.region().size;
|
let size = context.graphics.region().size;
|
||||||
let center = Point::from(size) / 2;
|
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 {
|
if let Some(measured) = &self.prepared_text {
|
||||||
context
|
context
|
||||||
|
|
@ -56,12 +56,17 @@ impl Widget for Label {
|
||||||
available_space: Size<crate::ConstraintLimit>,
|
available_space: Size<crate::ConstraintLimit>,
|
||||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||||
) -> Size<UPx> {
|
) -> 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);
|
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||||
self.text.map(|contents| {
|
self.text.map(|contents| {
|
||||||
let measured = context
|
let measured = context
|
||||||
.graphics
|
.graphics
|
||||||
.measure_text(Text::from(contents).wrap_at(width));
|
.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);
|
self.prepared_text = Some(measured);
|
||||||
size
|
size
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! A container that scrolls its contents on a virtual surface.
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ use kludgine::figures::{
|
||||||
use kludgine::shapes::Shape;
|
use kludgine::shapes::Shape;
|
||||||
use kludgine::Color;
|
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::context::{AsEventContext, EventContext};
|
||||||
use crate::styles::{
|
use crate::styles::{
|
||||||
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
|
ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent,
|
||||||
|
|
@ -82,6 +83,12 @@ impl Widget for Scroll {
|
||||||
ZeroToOne::ONE,
|
ZeroToOne::ONE,
|
||||||
Duration::from_millis(300),
|
Duration::from_millis(300),
|
||||||
)
|
)
|
||||||
|
.chain(Duration::from_secs(1))
|
||||||
|
.chain(Animation::linear(
|
||||||
|
self.scrollbar_opacity.clone(),
|
||||||
|
ZeroToOne::ZERO,
|
||||||
|
Duration::from_millis(300),
|
||||||
|
))
|
||||||
.spawn();
|
.spawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +108,7 @@ impl Widget for Scroll {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let visible_bottom_right = visible_rect.into_signed().extent();
|
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
|
let bar_width = styles
|
||||||
.get_or_default(&ScrollBarThickness)
|
.get_or_default(&ScrollBarThickness)
|
||||||
.into_px(context.graphics.scale());
|
.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;
|
pub struct ScrollBarThickness;
|
||||||
|
|
||||||
impl ComponentDefinition for ScrollBarThickness {
|
impl ComponentDefinition for ScrollBarThickness {
|
||||||
type ComponentType = Dimension;
|
type ComponentType = Dimension;
|
||||||
|
|
||||||
fn default_value(&self) -> Self::ComponentType {
|
fn default_value(&self) -> Self::ComponentType {
|
||||||
Dimension::Lp(Lp::points(9))
|
Dimension::Lp(Lp::points(7))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl<Layers> TileMap<Layers> {
|
||||||
fn construct(layers: Value<Layers>) -> Self {
|
fn construct(layers: Value<Layers>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
layers,
|
layers,
|
||||||
focus: Value::Constant(TileMapFocus::default()),
|
focus: Value::default(),
|
||||||
zoom: 1.,
|
zoom: 1.,
|
||||||
tick: None,
|
tick: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -366,7 +366,8 @@ where
|
||||||
) {
|
) {
|
||||||
match state {
|
match state {
|
||||||
ElementState::Pressed => {
|
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)) =
|
if let (ElementState::Pressed, Some(location), Some(hovered)) =
|
||||||
(state, &self.mouse_state.location, &self.mouse_state.widget)
|
(state, &self.mouse_state.location, &self.mouse_state.widget)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue