This commit is contained in:
2025-12-07 00:32:38 -05:00
parent 62aa02847a
commit 38266debb6
7 changed files with 68 additions and 32 deletions

View File

@@ -180,3 +180,17 @@ impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Ta
self self
} }
} }
impl std::ops::Deref for Span {
type Target = Vec<WidgetId>;
fn deref(&self) -> &Self::Target {
&self.children
}
}
impl std::ops::DerefMut for Span {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.children
}
}

View File

@@ -12,7 +12,7 @@ pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
impl<O, H: WidgetOption> TextBuilder<O, H> { impl<O, H: WidgetOption> TextBuilder<O, H> {
pub fn size(mut self, size: impl UiNum) -> Self { pub fn size(mut self, size: impl UiNum) -> Self {
self.attrs.font_size = size.to_f32(); self.attrs.font_size = size.to_f32();
self.attrs.line_height = self.attrs.font_size * 1.1; self.attrs.line_height = self.attrs.font_size * LINE_HEIGHT_MULT;
self self
} }
pub fn color(mut self, color: UiColor) -> Self { pub fn color(mut self, color: UiColor) -> Self {
@@ -31,6 +31,10 @@ impl<O, H: WidgetOption> TextBuilder<O, H> {
self.attrs.align = align.into(); self.attrs.align = align.into();
self self
} }
pub fn center_text(mut self) -> Self {
self.attrs.align = Align::CENTER;
self
}
pub fn wrap(mut self, wrap: bool) -> Self { pub fn wrap(mut self, wrap: bool) -> Self {
self.attrs.wrap = wrap; self.attrs.wrap = wrap;
self self

View File

@@ -1,6 +1,9 @@
use super::*; use super::*;
use crate::prelude::*; use crate::prelude::*;
// these methods should "not require any context" (require unit) because they're in core
event_ctx!(());
pub trait CoreWidget<W, Tag> { pub trait CoreWidget<W, Tag> {
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>; fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad>;
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>; fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned>;
@@ -100,8 +103,6 @@ impl<W: WidgetLike<Tag>, Tag> CoreWidget<W::Widget, Tag> for W {
} }
fn scroll(self) -> impl WidgetIdFn<Scroll> { fn scroll(self) -> impl WidgetIdFn<Scroll> {
event_ctx!(());
move |ui| { move |ui| {
Scroll::new(self.add(ui).any(), Axis::Y) Scroll::new(self.add(ui).any(), Axis::Y)
.on(CursorSense::Scroll, |ctx| { .on(CursorSense::Scroll, |ctx| {

View File

@@ -30,6 +30,7 @@ impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {
pub trait WidgetEventFn<Ctx, Data, W>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {} pub trait WidgetEventFn<Ctx, Data, W>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W> WidgetEventFn<Ctx, Data, W> for F {} impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W> WidgetEventFn<Ctx, Data, W> for F {}
// TODO: naming in here is a bit weird like eventable
#[macro_export] #[macro_export]
macro_rules! event_ctx { macro_rules! event_ctx {
($ty: ty) => { ($ty: ty) => {

View File

@@ -41,6 +41,11 @@ impl Size {
y: Len::ZERO, y: Len::ZERO,
}; };
pub const REST: Self = Self {
x: Len::REST,
y: Len::REST,
};
pub fn abs(v: Vec2) -> Self { pub fn abs(v: Vec2) -> Self {
Self { Self {
x: Len::abs(v.x), x: Len::abs(v.x),
@@ -97,6 +102,12 @@ impl Len {
rest: 0.0, rest: 0.0,
}; };
pub const REST: Self = Self {
abs: 0.0,
rel: 0.0,
rest: 1.0,
};
pub fn apply_rest(&self) -> UiScalar { pub fn apply_rest(&self) -> UiScalar {
UiScalar { UiScalar {
rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 }, rel: self.rel + if self.rest > 0.0 { 1.0 } else { 0.0 },

View File

@@ -63,7 +63,7 @@ impl Default for TextAttrs {
Self { Self {
color: UiColor::WHITE, color: UiColor::WHITE,
font_size: size, font_size: size,
line_height: size * 1.2, line_height: size * LINE_HEIGHT_MULT,
family: Family::SansSerif, family: Family::SansSerif,
wrap: false, wrap: false,
align: Align::CENTER_LEFT, align: Align::CENTER_LEFT,
@@ -71,6 +71,8 @@ impl Default for TextAttrs {
} }
} }
pub const LINE_HEIGHT_MULT: f32 = 1.1;
impl TextData { impl TextData {
pub fn draw( pub fn draw(
&mut self, &mut self,

View File

@@ -83,23 +83,26 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
let Self { let Self {
ui, ui,
ui_state: ui_data, ui_state,
app_state: app_data, app_state,
} = self; } = self;
let input_changed = ui_data.input.event(&event); let input_changed = ui_state.input.event(&event);
let cursor_state = ui_data.cursor_state().clone(); let cursor_state = ui_state.cursor_state().clone();
let old = ui_data.focus.clone(); let old = ui_state.focus.clone();
if cursor_state.buttons.left.is_start() { if cursor_state.buttons.left.is_start() {
ui_data.focus = None; ui_state.focus = None;
} }
if input_changed { if input_changed {
let window_size = ui_data.window_size(); let window_size = ui_state.window_size();
// call sensors with all 3 important contexts
// TODO: allow user to specify custom contexts?
// and give them both states in case they need both
ui.run_sensors(&mut (), &cursor_state, window_size); ui.run_sensors(&mut (), &cursor_state, window_size);
ui.run_sensors(ui_data, &cursor_state, window_size); ui.run_sensors(ui_state, &cursor_state, window_size);
ui.run_sensors(app_data, &cursor_state, window_size); ui.run_sensors(app_state, &cursor_state, window_size);
} }
if old != ui_data.focus if old != ui_state.focus
&& let Some(old) = old && let Some(old) = old
{ {
ui.text(&old).deselect(); ui.text(&old).deselect();
@@ -108,54 +111,54 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
ui.update(); ui.update();
ui_data.renderer.update(ui); ui_state.renderer.update(ui);
ui_data.renderer.draw(); ui_state.renderer.draw();
} }
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
ui.resize((size.width, size.height)); ui.resize((size.width, size.height));
ui_data.renderer.resize(size) ui_state.renderer.resize(size)
} }
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = &ui_data.focus if let Some(sel) = &ui_state.focus
&& event.state.is_pressed() && event.state.is_pressed()
{ {
let sel = &sel.clone(); let sel = &sel.clone();
let mut text = ui.text(sel); let mut text = ui.text(sel);
match text.apply_event(event, &ui_data.input.modifiers) { match text.apply_event(event, &ui_state.input.modifiers) {
TextInputResult::Unfocus => { TextInputResult::Unfocus => {
ui_data.focus = None; ui_state.focus = None;
ui_data.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
} }
TextInputResult::Submit => { TextInputResult::Submit => {
ui.run_event(app_data, sel, Submit, ()); ui.run_event(app_state, sel, Submit, ());
} }
TextInputResult::Paste => { TextInputResult::Paste => {
if let Ok(t) = ui_data.clipboard.get_text() { if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t); text.insert(&t);
} }
ui.run_event(app_data, sel, Edited, ()); ui.run_event(app_state, sel, Edited, ());
} }
TextInputResult::Copy(text) => { TextInputResult::Copy(text) => {
if let Err(err) = ui_data.clipboard.set_text(text) { if let Err(err) = ui_state.clipboard.set_text(text) {
eprintln!("failed to copy text to clipboard: {err}") eprintln!("failed to copy text to clipboard: {err}")
} }
} }
TextInputResult::Used => { TextInputResult::Used => {
ui.run_event(app_data, sel, Edited, ()); ui.run_event(app_state, sel, Edited, ());
} }
TextInputResult::Unused => {} TextInputResult::Unused => {}
} }
} }
} }
WindowEvent::Ime(ime) => { WindowEvent::Ime(ime) => {
if let Some(sel) = &ui_data.focus { if let Some(sel) = &ui_state.focus {
let mut text = ui.text(sel); let mut text = ui.text(sel);
match ime { match ime {
Ime::Enabled | Ime::Disabled => (), Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => { Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real // TODO: highlight once that's real
text.replace(ui_data.ime, content); text.replace(ui_state.ime, content);
ui_data.ime = content.chars().count(); ui_state.ime = content.chars().count();
} }
Ime::Commit(content) => { Ime::Commit(content) => {
text.insert(content); text.insert(content);
@@ -165,11 +168,11 @@ impl<State: DefaultAppState> AppState for DefaultState<State> {
} }
_ => (), _ => (),
} }
app_data.window_event(event, ui, ui_data); app_state.window_event(event, ui, ui_state);
if ui.needs_redraw() { if ui.needs_redraw() {
ui_data.renderer.window().request_redraw(); ui_state.renderer.window().request_redraw();
} }
ui_data.input.end_frame(); ui_state.input.end_frame();
} }
fn exit(&mut self) { fn exit(&mut self) {