mirror of
https://github.com/danbulant/cushy
synced 2026-06-20 23:11:12 +00:00
Progress towards an input widget
This commit is contained in:
parent
e04b1b14ad
commit
87578e5c76
18 changed files with 1016 additions and 206 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -561,6 +561,7 @@ name = "gooey"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alot",
|
||||
"interner",
|
||||
"kludgine",
|
||||
]
|
||||
|
||||
|
|
@ -703,6 +704,12 @@ dependencies = [
|
|||
"hashbrown 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interner"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8c60687056b35a996f2213287048a7092d801b61df5fee3bd5bd9bf6f17a2d0"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [
|
|||
"app",
|
||||
] }
|
||||
alot = "0.3"
|
||||
interner = "0.2.1"
|
||||
# appit = { git = "https://github.com/khonsulabs/appit" }
|
||||
|
||||
[patch."https://github.com/khonsulabs/kludgine"]
|
||||
|
|
|
|||
8
examples/input.rs
Normal file
8
examples/input.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use gooey::dynamic::Dynamic;
|
||||
use gooey::widget::Widget;
|
||||
use gooey::widgets::{Button, Input};
|
||||
use gooey::EventLoopError;
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
Input::new("Hello").run()
|
||||
}
|
||||
20
examples/style.rs
Normal file
20
examples/style.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use gooey::children::Children;
|
||||
use gooey::styles::{Styles, TextColor};
|
||||
use gooey::widgets::array::Array;
|
||||
use gooey::widgets::{Button, Style};
|
||||
use gooey::window::Window;
|
||||
use gooey::EventLoopError;
|
||||
use kludgine::Color;
|
||||
|
||||
fn main() -> Result<(), EventLoopError> {
|
||||
Window::for_widget(Array::rows(
|
||||
Children::new()
|
||||
.with_widget(Button::new("Default"))
|
||||
.with_widget(Style::new(
|
||||
Styles::new().with(&TextColor, Color::RED),
|
||||
Button::new("Styled"),
|
||||
)),
|
||||
))
|
||||
.styles(Styles::new().with(&TextColor, Color::GREEN))
|
||||
.run()
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use kludgine::app::winit::event::{DeviceId, KeyEvent, MouseButton};
|
||||
use kludgine::app::winit::event::{DeviceId, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase};
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{IntoSigned, Point, Rect, Size};
|
||||
use kludgine::Kludgine;
|
||||
|
||||
use crate::dynamic::Dynamic;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::tree::ManagedWidget;
|
||||
use crate::widget::{BoxedWidget, EventHandling};
|
||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
||||
use crate::widget::{BoxedWidget, EventHandling, ManagedWidget};
|
||||
use crate::window::RunningWindow;
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
|
|
@ -113,23 +114,41 @@ impl<'context, 'window> Context<'context, 'window> {
|
|||
device_id: DeviceId,
|
||||
input: KeyEvent,
|
||||
is_synthetic: bool,
|
||||
kludgine: &mut Kludgine,
|
||||
) -> EventHandling {
|
||||
self.current_node
|
||||
.lock()
|
||||
.keyboard_input(device_id, input, is_synthetic, self)
|
||||
.keyboard_input(device_id, input, is_synthetic, kludgine, self)
|
||||
}
|
||||
|
||||
pub fn mouse_wheel(
|
||||
&mut self,
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
) -> EventHandling {
|
||||
self.current_node
|
||||
.lock()
|
||||
.mouse_wheel(device_id, delta, phase, self)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn push_child(&self, child: BoxedWidget) -> ManagedWidget {
|
||||
self.current_node
|
||||
pub fn push_child(&mut self, child: BoxedWidget) -> ManagedWidget {
|
||||
let pushed_widget = self
|
||||
.current_node
|
||||
.tree
|
||||
.push_boxed(child, Some(self.current_node))
|
||||
.push_boxed(child, Some(self.current_node));
|
||||
pushed_widget
|
||||
.lock()
|
||||
.mounted(&mut self.for_other(&pushed_widget));
|
||||
pushed_widget
|
||||
}
|
||||
|
||||
pub fn remove_child(&self, child: ManagedWidget) {
|
||||
pub fn remove_child(&mut self, child: &ManagedWidget) {
|
||||
self.current_node
|
||||
.tree
|
||||
.remove_child(child, self.current_node);
|
||||
child.lock().unmounted(&mut self.for_other(child));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -263,6 +282,15 @@ impl<'context, 'window> Context<'context, 'window> {
|
|||
pub const fn widget(&self) -> &ManagedWidget {
|
||||
self.current_node
|
||||
}
|
||||
|
||||
pub fn attach_styles(&self, styles: Styles) {
|
||||
self.current_node.attach_styles(styles);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn query_style(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
|
||||
self.current_node.tree.query_style(self.current_node, query)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context<'_, '_> {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ pub mod children;
|
|||
pub mod context;
|
||||
pub mod dynamic;
|
||||
pub mod graphics;
|
||||
pub mod names;
|
||||
pub mod styles;
|
||||
mod tree;
|
||||
mod utils;
|
||||
pub mod widget;
|
||||
|
|
|
|||
35
src/names.rs
Normal file
35
src/names.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
|
||||
use interner::global::{GlobalString, StringPool};
|
||||
|
||||
static NAMES: StringPool = StringPool::new();
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Name(GlobalString);
|
||||
|
||||
impl Name {
|
||||
pub fn new<'a>(name: impl Into<Cow<'a, str>>) -> Self {
|
||||
Self(NAMES.get(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Name {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Name {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Name {
|
||||
fn from(value: String) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
315
src/styles.rs
Normal file
315
src/styles.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::names::Name;
|
||||
use crate::utils::Lazy;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Styles(Arc<HashMap<Group, HashMap<Name, Component>>>);
|
||||
|
||||
impl Styles {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn push(&mut self, name: &impl NamedComponent, component: impl Into<Component>) {
|
||||
let name = name.name().into_owned();
|
||||
Arc::make_mut(&mut self.0)
|
||||
.entry(name.group)
|
||||
.or_default()
|
||||
.insert(name.name, component.into());
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with(mut self, name: &impl NamedComponent, component: impl Into<Component>) -> Self {
|
||||
self.push(name, component);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get<Named>(&self, component: &Named) -> Option<&Component>
|
||||
where
|
||||
Named: NamedComponent + ?Sized,
|
||||
{
|
||||
let name = component.name();
|
||||
self.0
|
||||
.get(&name.group)
|
||||
.and_then(|group| group.get(&name.name))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_or_default<Named>(&self, component: &Named) -> Named::ComponentType
|
||||
where
|
||||
Named: ComponentDefinition + ?Sized,
|
||||
{
|
||||
let name = component.name();
|
||||
self.0
|
||||
.get(&name.group)
|
||||
.and_then(|group| group.get(&name.name))
|
||||
.and_then(|component| component.clone().try_into().ok())
|
||||
.unwrap_or_else(|| component.default_value())
|
||||
}
|
||||
}
|
||||
|
||||
pub type StyleQuery = Vec<ComponentName>;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
|
||||
use kludgine::figures::units::{Lp, Px};
|
||||
use kludgine::Color;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Component {
|
||||
Color(Color),
|
||||
Dimension(Dimension),
|
||||
Percent(f32),
|
||||
Boxed(BoxedComponent),
|
||||
}
|
||||
|
||||
impl From<Color> for Component {
|
||||
fn from(value: Color) -> Self {
|
||||
Self::Color(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for Color {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::Color(color) => Ok(color),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dimension> for Component {
|
||||
fn from(value: Dimension) -> Self {
|
||||
Self::Dimension(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for Dimension {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::Dimension(color) => Ok(color),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Px> for Component {
|
||||
fn from(value: Px) -> Self {
|
||||
Self::from(Dimension::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for Px {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::Dimension(Dimension::Px(px)) => Ok(px),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Lp> for Component {
|
||||
fn from(value: Lp) -> Self {
|
||||
Self::from(Dimension::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Component> for Lp {
|
||||
type Error = Component;
|
||||
|
||||
fn try_from(value: Component) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Component::Dimension(Dimension::Lp(px)) => Ok(px),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Dimension {
|
||||
Px(Px),
|
||||
Lp(Lp),
|
||||
}
|
||||
|
||||
impl From<Px> for Dimension {
|
||||
fn from(value: Px) -> Self {
|
||||
Self::Px(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Lp> for Dimension {
|
||||
fn from(value: Lp) -> Self {
|
||||
Self::Lp(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoxedComponent(Arc<dyn AnyComponent>);
|
||||
|
||||
impl BoxedComponent {
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: RefUnwindSafe + UnwindSafe + Debug + Send + Sync + 'static,
|
||||
{
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn downcast<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: Debug + Send + Sync + 'static,
|
||||
{
|
||||
self.0.as_ref().as_any().downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
trait AnyComponent: Send + Sync + RefUnwindSafe + UnwindSafe + Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl<T> AnyComponent for T
|
||||
where
|
||||
T: RefUnwindSafe + UnwindSafe + Debug + Send + Sync + 'static,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Group(Name);
|
||||
|
||||
impl Group {
|
||||
#[must_use]
|
||||
pub fn new<T>() -> Self
|
||||
where
|
||||
T: ComponentGroup,
|
||||
{
|
||||
Self(T::name())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn matches<T>(&self) -> bool
|
||||
where
|
||||
T: ComponentGroup,
|
||||
{
|
||||
self.0 == T::name()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentGroup {
|
||||
fn name() -> Name;
|
||||
}
|
||||
|
||||
pub enum Global {}
|
||||
|
||||
impl ComponentGroup for Global {
|
||||
fn name() -> Name {
|
||||
Name::new("global")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct ComponentName {
|
||||
pub group: Group,
|
||||
pub name: Name,
|
||||
}
|
||||
|
||||
impl ComponentName {
|
||||
pub fn new(group: Group, name: impl Into<Name>) -> Self {
|
||||
Self {
|
||||
group,
|
||||
name: name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named<G: ComponentGroup>(name: impl Into<Name>) -> Self {
|
||||
Self::new(Group::new::<G>(), name)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static Lazy<ComponentName>> for ComponentName {
|
||||
fn from(value: &'static Lazy<ComponentName>) -> Self {
|
||||
(**value).clone()
|
||||
}
|
||||
}
|
||||
pub trait NamedComponent {
|
||||
fn name(&self) -> Cow<'_, ComponentName>;
|
||||
}
|
||||
|
||||
pub trait ComponentDefinition: NamedComponent {
|
||||
type ComponentType: Into<Component> + TryFrom<Component, Error = Component>;
|
||||
|
||||
fn default_value(&self) -> Self::ComponentType;
|
||||
}
|
||||
|
||||
pub trait ComponentDefaultvalue: NamedComponent {
|
||||
fn default_component_value(&self) -> Component;
|
||||
}
|
||||
|
||||
impl<T> ComponentDefaultvalue for T
|
||||
where
|
||||
T: ComponentDefinition,
|
||||
{
|
||||
fn default_component_value(&self) -> Component {
|
||||
self.default_value().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedComponent for ComponentName {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedComponent for Cow<'_, ComponentName> {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct TextColor;
|
||||
|
||||
impl NamedComponent for TextColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("text_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for TextColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::WHITE
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct HighlightColor;
|
||||
|
||||
impl NamedComponent for HighlightColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Global>("highlight_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for HighlightColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::AQUA
|
||||
}
|
||||
}
|
||||
116
src/tree.rs
116
src/tree.rs
|
|
@ -1,12 +1,13 @@
|
|||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
|
||||
use alot::{LotId, Lots};
|
||||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::{Point, Rect};
|
||||
|
||||
use crate::widget::{BoxedWidget, Widget};
|
||||
use crate::styles::{ComponentDefaultvalue, Styles};
|
||||
use crate::widget::{BoxedWidget, ManagedWidget};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Tree {
|
||||
|
|
@ -14,13 +15,6 @@ pub struct Tree {
|
|||
}
|
||||
|
||||
impl Tree {
|
||||
pub fn push<W>(&self, widget: W, parent: Option<&ManagedWidget>) -> ManagedWidget
|
||||
where
|
||||
W: Widget,
|
||||
{
|
||||
self.push_boxed(BoxedWidget::new(widget), parent)
|
||||
}
|
||||
|
||||
pub fn push_boxed(&self, widget: BoxedWidget, parent: Option<&ManagedWidget>) -> ManagedWidget {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
let id = WidgetId(data.nodes.push(Node {
|
||||
|
|
@ -28,6 +22,7 @@ impl Tree {
|
|||
children: Vec::new(),
|
||||
parent: parent.map(|parent| parent.id),
|
||||
last_rendered_location: None,
|
||||
styles: None,
|
||||
}));
|
||||
if let Some(parent) = parent {
|
||||
let parent = &mut data.nodes[parent.id.0];
|
||||
|
|
@ -40,19 +35,18 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // This is sort of a destructor type call
|
||||
pub fn remove_child(&self, child: ManagedWidget, parent: &ManagedWidget) {
|
||||
pub fn remove_child(&self, child: &ManagedWidget, parent: &ManagedWidget) {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.remove_child(child.id, parent.id);
|
||||
}
|
||||
|
||||
fn note_rendered_rect(&self, widget: WidgetId, rect: Rect<Px>) {
|
||||
pub(crate) fn note_rendered_rect(&self, widget: WidgetId, rect: Rect<Px>) {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.nodes[widget.0].last_rendered_location = Some(rect);
|
||||
data.render_order.push(widget);
|
||||
}
|
||||
|
||||
fn last_rendered_at(&self, widget: WidgetId) -> Option<Rect<Px>> {
|
||||
pub(crate) fn last_rendered_at(&self, widget: WidgetId) -> Option<Rect<Px>> {
|
||||
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.nodes[widget.0].last_rendered_location
|
||||
}
|
||||
|
|
@ -131,6 +125,22 @@ impl Tree {
|
|||
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.nodes[id.0].parent
|
||||
}
|
||||
|
||||
pub(crate) fn attach_styles(&self, id: WidgetId, styles: Styles) {
|
||||
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
|
||||
data.nodes[id.0].styles = Some(styles);
|
||||
}
|
||||
|
||||
pub fn query_style(
|
||||
&self,
|
||||
perspective: &ManagedWidget,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
) -> Styles {
|
||||
self.data
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.query_style(perspective.id, query)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -180,63 +190,30 @@ impl TreeData {
|
|||
(None, _) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ManagedWidget {
|
||||
pub(crate) id: WidgetId,
|
||||
pub(crate) widget: BoxedWidget,
|
||||
pub(crate) tree: Tree,
|
||||
}
|
||||
|
||||
impl Debug for ManagedWidget {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ManagedWidget")
|
||||
.field("id", &self.id)
|
||||
.field("widget", &self.widget)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedWidget {
|
||||
pub(crate) fn lock(&self) -> MutexGuard<'_, dyn Widget> {
|
||||
self.widget.lock()
|
||||
}
|
||||
|
||||
pub(crate) fn note_rendered_rect(&self, rect: Rect<Px>) {
|
||||
self.tree.note_rendered_rect(self.id, rect);
|
||||
}
|
||||
|
||||
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
|
||||
self.tree.last_rendered_at(self.id)
|
||||
}
|
||||
|
||||
pub fn active(&self) -> bool {
|
||||
self.tree.active_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.tree.hovered_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
pub fn focused(&self) -> bool {
|
||||
self.tree.focused_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<ManagedWidget> {
|
||||
self.tree.parent(self.id).map(|id| self.tree.widget(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ManagedWidget {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.widget == other.widget
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<BoxedWidget> for ManagedWidget {
|
||||
fn eq(&self, other: &BoxedWidget) -> bool {
|
||||
&self.widget == other
|
||||
fn query_style(
|
||||
&self,
|
||||
mut perspective: WidgetId,
|
||||
query: &[&dyn ComponentDefaultvalue],
|
||||
) -> Styles {
|
||||
let mut query = query.iter().map(|n| n.name()).collect::<Vec<_>>();
|
||||
let mut resolved = Styles::new();
|
||||
while !query.is_empty() {
|
||||
let node = &self.nodes[perspective.0];
|
||||
if let Some(styles) = &node.styles {
|
||||
query.retain(|name| {
|
||||
if let Some(component) = styles.get(name) {
|
||||
resolved.push(name, component.clone());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
let Some(parent) = node.parent else { break };
|
||||
perspective = parent;
|
||||
}
|
||||
resolved
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,6 +222,7 @@ pub struct Node {
|
|||
pub children: Vec<WidgetId>,
|
||||
pub parent: Option<WidgetId>,
|
||||
pub last_rendered_location: Option<Rect<Px>>,
|
||||
pub styles: Option<Styles>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
|
|
|
|||
25
src/utils.rs
25
src/utils.rs
|
|
@ -1,3 +1,6 @@
|
|||
use std::ops::Deref;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use kludgine::app::winit::keyboard::ModifiersState;
|
||||
|
||||
pub trait ModifiersExt {
|
||||
|
|
@ -15,3 +18,25 @@ impl ModifiersExt for ModifiersState {
|
|||
self.control_key()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lazy<T> {
|
||||
init: fn() -> T,
|
||||
once: OnceLock<T>,
|
||||
}
|
||||
|
||||
impl<T> Lazy<T> {
|
||||
pub const fn new(init: fn() -> T) -> Self {
|
||||
Self {
|
||||
init,
|
||||
once: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Lazy<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.once.get_or_init(self.init)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
127
src/widget.rs
127
src/widget.rs
|
|
@ -5,13 +5,16 @@ use std::panic::UnwindSafe;
|
|||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::app::winit::event::{DeviceId, KeyEvent, MouseButton};
|
||||
use kludgine::app::winit::event::{DeviceId, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase};
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{Point, Size};
|
||||
use kludgine::figures::{Point, Rect, Size};
|
||||
use kludgine::Kludgine;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::dynamic::Dynamic;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::{Component, Group, Styles};
|
||||
use crate::tree::{Tree, WidgetId};
|
||||
use crate::window::{RunningWindow, Window, WindowBehavior};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
|
|
@ -20,7 +23,7 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Window::<WidgetWindow<Self>>::new(WidgetWindow(Some(self))).run()
|
||||
Window::<BoxedWidget>::new(BoxedWidget::new(self)).run()
|
||||
}
|
||||
|
||||
fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>);
|
||||
|
|
@ -32,6 +35,11 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
context: &mut Context<'_, '_>,
|
||||
) -> Size<UPx>;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn mounted(&mut self, context: &mut Context<'_, '_>) {}
|
||||
#[allow(unused_variables)]
|
||||
fn unmounted(&mut self, context: &mut Context<'_, '_>) {}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn hit_test(&mut self, location: Point<Px>, context: &mut Context<'_, '_>) -> bool {
|
||||
false
|
||||
|
|
@ -92,10 +100,27 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
|
|||
device_id: DeviceId,
|
||||
input: KeyEvent,
|
||||
is_synthetic: bool,
|
||||
kludgine: &mut Kludgine,
|
||||
context: &mut Context<'_, '_>,
|
||||
) -> EventHandling {
|
||||
UNHANDLED
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn mouse_wheel(
|
||||
&mut self,
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
context: &mut Context<'_, '_>,
|
||||
) -> EventHandling {
|
||||
UNHANDLED
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn query_component(&self, group: Group, name: &str) -> Option<Component> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub type EventHandling = ControlFlow<EventHandled, EventIgnored>;
|
||||
|
|
@ -108,23 +133,6 @@ pub struct EventIgnored;
|
|||
pub const HANDLED: EventHandling = EventHandling::Break(EventHandled);
|
||||
pub const UNHANDLED: EventHandling = EventHandling::Continue(EventIgnored);
|
||||
|
||||
struct WidgetWindow<W>(Option<W>);
|
||||
|
||||
impl<T> WindowBehavior for WidgetWindow<T>
|
||||
where
|
||||
T: Widget + Send + UnwindSafe,
|
||||
{
|
||||
type Context = Self;
|
||||
|
||||
fn initialize(_window: &mut RunningWindow<'_>, context: Self::Context) -> Self {
|
||||
context
|
||||
}
|
||||
|
||||
fn make_root(&mut self, tree: &crate::tree::Tree) -> crate::tree::ManagedWidget {
|
||||
tree.push(self.0.take().expect("root already created"), None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoxedWidget(Arc<Mutex<dyn Widget>>);
|
||||
|
||||
|
|
@ -149,6 +157,18 @@ impl PartialEq for BoxedWidget {
|
|||
}
|
||||
}
|
||||
|
||||
impl WindowBehavior for BoxedWidget {
|
||||
type Context = Self;
|
||||
|
||||
fn initialize(_window: &mut RunningWindow<'_>, context: Self::Context) -> Self {
|
||||
context
|
||||
}
|
||||
|
||||
fn make_root(&mut self) -> BoxedWidget {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value<T>
|
||||
where
|
||||
|
|
@ -244,3 +264,70 @@ where
|
|||
self(value);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ManagedWidget {
|
||||
pub(crate) id: WidgetId,
|
||||
pub(crate) widget: BoxedWidget,
|
||||
pub(crate) tree: Tree,
|
||||
}
|
||||
|
||||
impl Debug for ManagedWidget {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ManagedWidget")
|
||||
.field("id", &self.id)
|
||||
.field("widget", &self.widget)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagedWidget {
|
||||
pub(crate) fn lock(&self) -> MutexGuard<'_, dyn Widget> {
|
||||
self.widget.lock()
|
||||
}
|
||||
|
||||
pub(crate) fn note_rendered_rect(&self, rect: Rect<Px>) {
|
||||
self.tree.note_rendered_rect(self.id, rect);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn last_rendered_at(&self) -> Option<Rect<Px>> {
|
||||
self.tree.last_rendered_at(self.id)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn active(&self) -> bool {
|
||||
self.tree.active_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.tree.hovered_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn focused(&self) -> bool {
|
||||
self.tree.focused_widget() == Some(self.id)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parent(&self) -> Option<ManagedWidget> {
|
||||
self.tree.parent(self.id).map(|id| self.tree.widget(id))
|
||||
}
|
||||
|
||||
pub(crate) fn attach_styles(&self, styles: Styles) {
|
||||
self.tree.attach_styles(self.id, styles);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ManagedWidget {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.widget == other.widget
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<BoxedWidget> for ManagedWidget {
|
||||
fn eq(&self, other: &BoxedWidget) -> bool {
|
||||
&self.widget == other
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
pub mod array;
|
||||
mod button;
|
||||
mod canvas;
|
||||
mod input;
|
||||
mod label;
|
||||
mod style;
|
||||
|
||||
pub use button::Button;
|
||||
pub use canvas::Canvas;
|
||||
pub use input::Input;
|
||||
pub use label::Label;
|
||||
pub use style::Style;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ use kludgine::figures::{Point, Rect, Size};
|
|||
use crate::children::Children;
|
||||
use crate::context::Context;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::tree::ManagedWidget;
|
||||
use crate::widget::{IntoValue, Value, Widget};
|
||||
use crate::widget::{IntoValue, ManagedWidget, Value, Widget};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -83,7 +82,7 @@ impl Array {
|
|||
// Any children remaining at the end of this process are ones
|
||||
// that have been removed.
|
||||
for removed in self.synced_children.drain(children.len()..) {
|
||||
context.remove_child(removed);
|
||||
context.remove_child(&removed);
|
||||
}
|
||||
self.layout.truncate(children.len());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
use std::borrow::Cow;
|
||||
use std::panic::UnwindSafe;
|
||||
|
||||
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
||||
use kludgine::app::winit::keyboard::KeyCode;
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{Point, Rect, Size};
|
||||
use kludgine::figures::{IntoUnsigned, Point, Rect, Size};
|
||||
use kludgine::shapes::{Shape, StrokeOptions};
|
||||
use kludgine::Color;
|
||||
use kludgine::{Color, Kludgine};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::names::Name;
|
||||
use crate::styles::{
|
||||
ComponentDefinition, ComponentGroup, ComponentName, HighlightColor, NamedComponent, TextColor,
|
||||
};
|
||||
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -50,21 +55,32 @@ impl Widget for Button {
|
|||
context.redraw_when_changed(label);
|
||||
}
|
||||
|
||||
let styles = context.query_style(&[
|
||||
&TextColor,
|
||||
&HighlightColor,
|
||||
&ButtonActiveBackground,
|
||||
&ButtonBackground,
|
||||
&ButtonHoverBackground,
|
||||
]);
|
||||
|
||||
let visible_rect = Rect::from(graphics.size() - (UPx(1), UPx(1)));
|
||||
|
||||
let background = if context.active() {
|
||||
Color::new(30, 30, 30, 255)
|
||||
styles.get_or_default(&ButtonActiveBackground)
|
||||
} else if context.hovered() {
|
||||
Color::new(40, 40, 40, 255)
|
||||
styles.get_or_default(&ButtonHoverBackground)
|
||||
} else {
|
||||
Color::new(10, 10, 10, 255)
|
||||
styles.get_or_default(&ButtonBackground)
|
||||
};
|
||||
let background = Shape::filled_rect(visible_rect, background);
|
||||
graphics.draw_shape(&background, Point::default(), None, None);
|
||||
|
||||
if context.focused() {
|
||||
let focus_ring =
|
||||
Shape::stroked_rect(visible_rect, Color::AQUA, StrokeOptions::default());
|
||||
let focus_ring = Shape::stroked_rect(
|
||||
visible_rect,
|
||||
styles.get_or_default(&HighlightColor),
|
||||
StrokeOptions::default(),
|
||||
);
|
||||
graphics.draw_shape(&focus_ring, Point::default(), None, None);
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +88,7 @@ impl Widget for Button {
|
|||
self.label.map(|label| {
|
||||
graphics.draw_text(
|
||||
label,
|
||||
Color::WHITE,
|
||||
styles.get_or_default(&TextColor),
|
||||
kludgine::text::TextOrigin::Center,
|
||||
center,
|
||||
None,
|
||||
|
|
@ -159,11 +175,11 @@ impl Widget for Button {
|
|||
) -> Size<UPx> {
|
||||
let width = available_space.width.max().try_into().unwrap_or(Px::MAX);
|
||||
self.label.map(|label| {
|
||||
graphics
|
||||
.measure_text::<Px>(label, Color::RED, Some(width))
|
||||
.size
|
||||
.try_cast::<UPx>()
|
||||
.unwrap_or_default()
|
||||
let measured = graphics.measure_text::<Px>(label, Color::WHITE, Some(width));
|
||||
|
||||
let mut size = measured.size.into_unsigned();
|
||||
size.height = size.height.max(measured.line_height.into_unsigned());
|
||||
size
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +188,7 @@ impl Widget for Button {
|
|||
_device_id: DeviceId,
|
||||
input: KeyEvent,
|
||||
_is_synthetic: bool,
|
||||
kludgine: &mut Kludgine,
|
||||
context: &mut Context<'_, '_>,
|
||||
) -> EventHandling {
|
||||
if input.physical_key == KeyCode::Space {
|
||||
|
|
@ -215,3 +232,60 @@ impl Widget for Button {
|
|||
context.set_needs_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentGroup for Button {
|
||||
fn name() -> Name {
|
||||
Name::new("button")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonBackground;
|
||||
|
||||
impl NamedComponent for ButtonBackground {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Button>("background_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for ButtonBackground {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::new(10, 10, 10, 255)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonActiveBackground;
|
||||
|
||||
impl NamedComponent for ButtonActiveBackground {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Button>("active_background_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for ButtonActiveBackground {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::new(30, 30, 30, 255)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct ButtonHoverBackground;
|
||||
|
||||
impl NamedComponent for ButtonHoverBackground {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::named::<Button>("hover_background_color"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDefinition for ButtonHoverBackground {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self) -> Color {
|
||||
Color::new(40, 40, 40, 255)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
169
src/widgets/input.rs
Normal file
169
src/widgets/input.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use kludgine::app::winit::keyboard::Key;
|
||||
use kludgine::cosmic_text::{Action, Attrs, Buffer, Edit, Editor, FontSystem, Metrics, Shaping};
|
||||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::{FloatConversion, IntoUnsigned, Point};
|
||||
use kludgine::text::TextOrigin;
|
||||
use kludgine::{Color, Kludgine};
|
||||
|
||||
use crate::styles::{Styles, TextColor};
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::widget::{EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||
|
||||
#[must_use]
|
||||
pub struct Input {
|
||||
pub text: Value<String>,
|
||||
editor: Option<LiveEditor>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn empty() -> Self {
|
||||
Self::new(String::new())
|
||||
}
|
||||
|
||||
pub fn new(initial_text: impl IntoValue<String>) -> Self {
|
||||
Self {
|
||||
text: initial_text.into_value(),
|
||||
editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn editor_mut(&mut self, font_system: &mut FontSystem, styles: &Styles) -> &mut Editor {
|
||||
match (&self.editor, self.text.generation()) {
|
||||
(Some(editor), generation) if editor.generation == generation => {}
|
||||
(_, generation) => {
|
||||
let mut buffer = Buffer::new(font_system, Metrics::new(12., 18.));
|
||||
self.text.map(|text| {
|
||||
buffer.set_text(font_system, text, Attrs::new(), Shaping::Advanced);
|
||||
});
|
||||
self.editor = Some(LiveEditor {
|
||||
editor: Editor::new(buffer),
|
||||
generation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
&mut self.editor.as_mut().expect("just initialized").editor
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Input {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Input")
|
||||
.field("text", &self.text)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Input {
|
||||
fn hit_test(
|
||||
&mut self,
|
||||
location: Point<Px>,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn mouse_down(
|
||||
&mut self,
|
||||
location: Point<Px>,
|
||||
device_id: kludgine::app::winit::event::DeviceId,
|
||||
button: kludgine::app::winit::event::MouseButton,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) -> EventHandling {
|
||||
// self.editor_mut(, styles);
|
||||
|
||||
HANDLED
|
||||
}
|
||||
|
||||
fn mouse_up(
|
||||
&mut self,
|
||||
location: Option<Point<Px>>,
|
||||
device_id: kludgine::app::winit::event::DeviceId,
|
||||
button: kludgine::app::winit::event::MouseButton,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) {
|
||||
context.focus();
|
||||
}
|
||||
|
||||
fn redraw(
|
||||
&mut self,
|
||||
graphics: &mut crate::graphics::Graphics<'_, '_, '_>,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) {
|
||||
let size = graphics.size();
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let editor = self.editor_mut(graphics.font_system(), &styles);
|
||||
let buffer = editor.buffer_mut();
|
||||
buffer.set_size(
|
||||
graphics.font_system(),
|
||||
size.width.into_float(),
|
||||
size.height.into_float(),
|
||||
);
|
||||
buffer.shape_until_scroll(graphics.font_system());
|
||||
graphics.draw_text_buffer(
|
||||
buffer,
|
||||
styles.get_or_default(&TextColor),
|
||||
TextOrigin::TopLeft,
|
||||
Point::<Px>::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: kludgine::figures::Size<crate::ConstraintLimit>,
|
||||
graphics: &mut crate::graphics::Graphics<'_, '_, '_>,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) -> kludgine::figures::Size<kludgine::figures::units::UPx> {
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let editor = self.editor_mut(graphics.font_system(), &styles);
|
||||
let buffer = editor.buffer_mut();
|
||||
buffer.set_size(
|
||||
graphics.font_system(),
|
||||
available_space.width.max().into_float(),
|
||||
available_space.height.max().into_float(),
|
||||
);
|
||||
graphics
|
||||
.measure_text_buffer::<Px>(buffer, Color::WHITE)
|
||||
.size
|
||||
.into_unsigned()
|
||||
}
|
||||
|
||||
fn keyboard_input(
|
||||
&mut self,
|
||||
device_id: kludgine::app::winit::event::DeviceId,
|
||||
input: kludgine::app::winit::event::KeyEvent,
|
||||
is_synthetic: bool,
|
||||
kludgine: &mut Kludgine,
|
||||
context: &mut crate::context::Context<'_, '_>,
|
||||
) -> EventHandling {
|
||||
if !input.state.is_pressed() {
|
||||
return UNHANDLED;
|
||||
}
|
||||
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let editor = &mut self.editor.as_mut().expect("input without editor").editor;
|
||||
|
||||
match (input.logical_key, input.text) {
|
||||
(Key::Backspace, _) => {
|
||||
editor.action(kludgine.font_system(), Action::Backspace);
|
||||
context.set_needs_redraw();
|
||||
HANDLED
|
||||
}
|
||||
(_, Some(text)) if !context.modifiers().state().primary() => {
|
||||
editor.insert_string(&text, None);
|
||||
context.set_needs_redraw();
|
||||
HANDLED
|
||||
}
|
||||
(_, _) => UNHANDLED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveEditor {
|
||||
editor: Editor,
|
||||
generation: Option<usize>,
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ use kludgine::Color;
|
|||
|
||||
use crate::context::Context;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::TextColor;
|
||||
use crate::widget::{IntoValue, Value, Widget};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -26,11 +27,12 @@ impl Widget for Label {
|
|||
if let Value::Dynamic(contents) = &mut self.contents {
|
||||
context.redraw_when_changed(contents);
|
||||
}
|
||||
let styles = context.query_style(&[&TextColor]);
|
||||
let width = graphics.size().width;
|
||||
self.contents.map(|contents| {
|
||||
graphics.draw_text(
|
||||
contents,
|
||||
Color::RED,
|
||||
styles.get_or_default(&TextColor),
|
||||
TextOrigin::Center,
|
||||
center,
|
||||
None,
|
||||
|
|
|
|||
68
src/widgets/style.rs
Normal file
68
src/widgets/style.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use kludgine::figures::units::UPx;
|
||||
use kludgine::figures::Size;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::styles::Styles;
|
||||
use crate::widget::{BoxedWidget, ManagedWidget, Widget};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Style {
|
||||
styles: Styles,
|
||||
child: BoxedWidget,
|
||||
mounted_child: Option<ManagedWidget>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn new<W>(styles: Styles, child: W) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
{
|
||||
Self {
|
||||
styles,
|
||||
child: BoxedWidget::new(child),
|
||||
mounted_child: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Style {
|
||||
fn mounted(&mut self, context: &mut Context<'_, '_>) {
|
||||
context.attach_styles(self.styles.clone());
|
||||
self.mounted_child = Some(context.push_child(self.child.clone()));
|
||||
}
|
||||
|
||||
fn unmounted(&mut self, context: &mut Context<'_, '_>) {
|
||||
let child = self
|
||||
.mounted_child
|
||||
.take()
|
||||
.expect("unmounted without being mounted");
|
||||
context.remove_child(&child);
|
||||
}
|
||||
|
||||
fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>) {
|
||||
context
|
||||
.for_other(
|
||||
self.mounted_child
|
||||
.as_ref()
|
||||
.expect("measuring without being mounted"),
|
||||
)
|
||||
.redraw(graphics);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
graphics: &mut Graphics<'_, '_, '_>,
|
||||
context: &mut Context<'_, '_>,
|
||||
) -> Size<UPx> {
|
||||
context
|
||||
.for_other(
|
||||
self.mounted_child
|
||||
.as_ref()
|
||||
.expect("measuring without being mounted"),
|
||||
)
|
||||
.measure(available_space, graphics)
|
||||
}
|
||||
}
|
||||
172
src/window.rs
172
src/window.rs
|
|
@ -4,29 +4,35 @@ use std::panic::{AssertUnwindSafe, UnwindSafe};
|
|||
|
||||
use kludgine::app::winit::dpi::PhysicalPosition;
|
||||
use kludgine::app::winit::error::EventLoopError;
|
||||
use kludgine::app::winit::event::{DeviceId, ElementState, KeyEvent, MouseButton};
|
||||
use kludgine::app::winit::event::{
|
||||
DeviceId, ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
};
|
||||
use kludgine::app::winit::keyboard::KeyCode;
|
||||
use kludgine::app::WindowBehavior as _;
|
||||
use kludgine::figures::units::Px;
|
||||
use kludgine::figures::Point;
|
||||
use kludgine::render::Drawing;
|
||||
use kludgine::Kludgine;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::graphics::Graphics;
|
||||
use crate::tree::{ManagedWidget, Tree};
|
||||
use crate::styles::Styles;
|
||||
use crate::tree::Tree;
|
||||
use crate::utils::ModifiersExt;
|
||||
use crate::widget::{EventHandling, HANDLED, UNHANDLED};
|
||||
use crate::widget::{BoxedWidget, EventHandling, ManagedWidget, Widget, HANDLED, UNHANDLED};
|
||||
use crate::window::sealed::WindowCommand;
|
||||
|
||||
pub type RunningWindow<'window> = kludgine::app::Window<'window, WindowCommand>;
|
||||
pub type WindowAttributes = kludgine::app::WindowAttributes<WindowCommand>;
|
||||
|
||||
#[must_use]
|
||||
pub struct Window<Behavior>
|
||||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
context: Behavior::Context,
|
||||
pub attributes: WindowAttributes,
|
||||
pub styles: Option<Styles>,
|
||||
}
|
||||
|
||||
impl<Behavior> Default for Window<Behavior>
|
||||
|
|
@ -40,21 +46,42 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl Window<BoxedWidget> {
|
||||
pub fn for_widget<W>(widget: W) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
{
|
||||
Self::new(BoxedWidget::new(widget))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Behavior> Window<Behavior>
|
||||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
pub fn new(context: Behavior::Context) -> Self {
|
||||
Self {
|
||||
attributes: WindowAttributes::default(),
|
||||
attributes: WindowAttributes {
|
||||
title: String::from("Gooey App"),
|
||||
..WindowAttributes::default()
|
||||
},
|
||||
context,
|
||||
styles: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn styles(mut self, styles: Styles) -> Self {
|
||||
self.styles = Some(styles);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<(), EventLoopError> {
|
||||
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe((
|
||||
self.context,
|
||||
RefCell::new(Some(self.attributes)),
|
||||
RefCell::new(WindowSettings {
|
||||
styles: self.styles,
|
||||
attributes: Some(self.attributes),
|
||||
}),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +91,7 @@ pub trait WindowBehavior: Sized + 'static {
|
|||
|
||||
fn initialize(window: &mut RunningWindow<'_>, context: Self::Context) -> Self;
|
||||
|
||||
fn make_root(&mut self, tree: &Tree) -> ManagedWidget;
|
||||
fn make_root(&mut self) -> BoxedWidget;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn close_requested(&self, window: &mut RunningWindow<'_>) -> bool {
|
||||
|
|
@ -79,13 +106,7 @@ pub trait WindowBehavior: Sized + 'static {
|
|||
}
|
||||
|
||||
fn run_with(context: Self::Context) -> Result<(), EventLoopError> {
|
||||
GooeyWindow::<Self>::run_with(AssertUnwindSafe((
|
||||
context,
|
||||
RefCell::new(Some(WindowAttributes {
|
||||
title: String::from("Gooey Application"),
|
||||
..WindowAttributes::default()
|
||||
})),
|
||||
)))
|
||||
Window::<Self>::new(context).run()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +133,7 @@ impl<T> kludgine::app::WindowBehavior<WindowCommand> for GooeyWindow<T>
|
|||
where
|
||||
T: WindowBehavior,
|
||||
{
|
||||
type Context = AssertUnwindSafe<(T::Context, RefCell<Option<WindowAttributes>>)>;
|
||||
type Context = AssertUnwindSafe<(T::Context, RefCell<WindowSettings>)>;
|
||||
|
||||
fn initialize(
|
||||
mut window: RunningWindow<'_>,
|
||||
|
|
@ -120,7 +141,10 @@ where
|
|||
context: Self::Context,
|
||||
) -> Self {
|
||||
let mut behavior = T::initialize(&mut window, context.0 .0);
|
||||
let root = behavior.make_root(&Tree::default());
|
||||
let root = Tree::default().push_boxed(behavior.make_root(), None);
|
||||
if let Some(styles) = context.0 .1.borrow_mut().styles.take() {
|
||||
root.attach_styles(styles);
|
||||
}
|
||||
Self {
|
||||
behavior,
|
||||
root,
|
||||
|
|
@ -152,10 +176,13 @@ where
|
|||
!self.should_close
|
||||
}
|
||||
|
||||
fn initial_window_attributes(context: &Self::Context) -> WindowAttributes {
|
||||
fn initial_window_attributes(
|
||||
context: &Self::Context,
|
||||
) -> kludgine::app::WindowAttributes<WindowCommand> {
|
||||
context
|
||||
.1
|
||||
.borrow_mut()
|
||||
.attributes
|
||||
.take()
|
||||
.expect("called more than once")
|
||||
}
|
||||
|
|
@ -200,17 +227,18 @@ where
|
|||
device_id: DeviceId,
|
||||
input: KeyEvent,
|
||||
is_synthetic: bool,
|
||||
kludgine: &mut Kludgine,
|
||||
) {
|
||||
let handled = if let Some(focus) = self.root.tree.focused_widget() {
|
||||
let focus = self.root.tree.widget(focus);
|
||||
let mut focus = Context::new(&focus, &mut window);
|
||||
recursively_handle_event(&mut focus, |widget| {
|
||||
widget.keyboard_input(device_id, input.clone(), is_synthetic)
|
||||
})
|
||||
.is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let target = self.root.tree.hovered_widget().unwrap_or(self.root.id);
|
||||
let target = self.root.tree.widget(target);
|
||||
let mut target = Context::new(&target, &mut window);
|
||||
|
||||
let handled = recursively_handle_event(&mut target, |widget| {
|
||||
widget.keyboard_input(device_id, input.clone(), is_synthetic, kludgine)
|
||||
})
|
||||
.is_some();
|
||||
drop(target);
|
||||
|
||||
if !handled && !input.state.is_pressed() {
|
||||
match input.physical_key {
|
||||
KeyCode::KeyW if window.modifiers().state().primary() => {
|
||||
|
|
@ -223,6 +251,22 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn mouse_wheel(
|
||||
&mut self,
|
||||
mut window: RunningWindow<'_>,
|
||||
device_id: DeviceId,
|
||||
delta: MouseScrollDelta,
|
||||
phase: TouchPhase,
|
||||
) {
|
||||
let widget = self.root.tree.hovered_widget().unwrap_or(self.root.id);
|
||||
|
||||
let widget = self.root.tree.widget(widget);
|
||||
let mut widget = Context::new(&widget, &mut window);
|
||||
recursively_handle_event(&mut widget, |widget| {
|
||||
widget.mouse_wheel(device_id, delta, phase)
|
||||
});
|
||||
}
|
||||
|
||||
// fn modifiers_changed(&mut self, window: kludgine::app::Window<'_, ()>) {}
|
||||
|
||||
// fn ime(
|
||||
|
|
@ -251,6 +295,7 @@ where
|
|||
} else {
|
||||
// Hover
|
||||
let mut context = Context::new(&self.root, &mut window);
|
||||
self.mouse_state.widget = None;
|
||||
for widget in self.root.tree.widgets_at_point(location) {
|
||||
let mut widget_context = context.for_other(&widget);
|
||||
let relative = location
|
||||
|
|
@ -266,16 +311,13 @@ where
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if self.mouse_state.widget.is_none() {
|
||||
context.clear_hover();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn cursor_entered(
|
||||
// &mut self,
|
||||
// window: RunningWindow<'_>,
|
||||
// device_id: DeviceId,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
fn cursor_left(&mut self, mut window: RunningWindow<'_>, _device_id: DeviceId) {
|
||||
if self.mouse_state.widget.take().is_some() {
|
||||
let mut context = Context::new(&self.root, &mut window);
|
||||
|
|
@ -283,15 +325,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// fn mouse_wheel(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// delta: kludgine::app::winit::event::MouseScrollDelta,
|
||||
// phase: kludgine::app::winit::event::TouchPhase,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
fn mouse_input(
|
||||
&mut self,
|
||||
mut window: RunningWindow<'_>,
|
||||
|
|
@ -348,56 +381,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// fn touchpad_pressure(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// pressure: f32,
|
||||
// stage: i64,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
// fn axis_motion(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// axis: kludgine::app::winit::event::AxisId,
|
||||
// value: f64,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
// fn touch(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// touch: kludgine::app::winit::event::Touch,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
// fn touchpad_magnify(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// delta: f64,
|
||||
// phase: kludgine::app::winit::event::TouchPhase,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
// fn smart_magnify(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
// fn touchpad_rotate(
|
||||
// &mut self,
|
||||
// window: kludgine::app::Window<'_, ()>,
|
||||
// device_id: kludgine::app::winit::event::DeviceId,
|
||||
// delta: f32,
|
||||
// phase: kludgine::app::winit::event::TouchPhase,
|
||||
// ) {
|
||||
// }
|
||||
|
||||
fn event(&mut self, event: WindowCommand, mut window: RunningWindow<'_>) {
|
||||
match event {
|
||||
WindowCommand::Redraw => {
|
||||
|
|
@ -419,6 +402,11 @@ fn recursively_handle_event(
|
|||
}
|
||||
}
|
||||
|
||||
pub struct WindowSettings {
|
||||
styles: Option<Styles>,
|
||||
attributes: Option<WindowAttributes>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MouseState {
|
||||
location: Option<Point<Px>>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue