Compare commits

1 Commits

Author SHA1 Message Date
1cec56e847 TEXT SELECTION 2025-11-21 23:56:31 -05:00
10 changed files with 332 additions and 69 deletions

View File

@@ -6,7 +6,12 @@ use iris::prelude::*;
use len_fns::*; use len_fns::*;
use render::Renderer; use render::Renderer;
use std::sync::Arc; use std::sync::Arc;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use winit::{
dpi::{LogicalPosition, LogicalSize},
event::{Ime, WindowEvent},
event_loop::ActiveEventLoop,
window::Window,
};
mod app; mod app;
mod input; mod input;
@@ -18,11 +23,13 @@ fn main() {
pub struct Client { pub struct Client {
renderer: Renderer, renderer: Renderer,
window: Arc<Window>,
input: Input, input: Input,
ui: Ui, ui: Ui,
info: WidgetId<Text>, info: WidgetId<Text>,
focus: Option<WidgetId<TextEdit>>, focus: Option<WidgetId<TextEdit>>,
clipboard: Clipboard, clipboard: Clipboard,
ime: usize,
} }
#[derive(Eq, PartialEq, Hash, Clone)] #[derive(Eq, PartialEq, Hash, Clone)]
@@ -32,9 +39,37 @@ impl DefaultEvent for Submit {
type Data = (); type Data = ();
} }
pub struct Selectable;
impl WidgetAttr<TextEdit> for Selectable {
type Input = ();
fn run(ui: &mut Ui, id: &WidgetId<TextEdit>, _: Self::Input) {
let id = id.clone();
ui.register_event(
&id.clone(),
CursorSense::click_or_drag(),
move |client: &mut Client, data| {
client
.ui
.text(&id)
.select(data.cursor, data.size, data.sense.is_dragging());
if let Some(region) = client.ui.window_region(&id) {
client.window.set_ime_allowed(true);
client.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()),
LogicalSize::<f32>::from(region.size().tuple()),
);
}
client.focus = Some(id.clone());
},
);
}
}
impl Client { impl Client {
pub fn new(window: Arc<Window>) -> Self { pub fn new(window: Arc<Window>) -> Self {
let renderer = Renderer::new(window); let renderer = Renderer::new(window.clone());
let mut ui = Ui::new(); let mut ui = Ui::new();
let rrect = rect(Color::WHITE).radius(20); let rrect = rect(Color::WHITE).radius(20);
@@ -127,10 +162,7 @@ impl Client {
.editable() .editable()
.text_align(Align::LEFT) .text_align(Align::LEFT)
.size(30) .size(30)
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| { .attr::<Selectable>(())
client.ui.text(id).select(ctx.cursor, ctx.size);
client.focus = Some(id.clone());
})
.id_on(Submit, move |id, client: &mut Client, _| { .id_on(Submit, move |id, client: &mut Client, _| {
let content = client.ui.text(id).take(); let content = client.ui.text(id).take();
let text = text(content) let text = text(content)
@@ -138,10 +170,7 @@ impl Client {
.size(30) .size(30)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.wrap(true) .wrap(true)
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| { .attr::<Selectable>(());
client.ui.text(id).select(ctx.cursor, ctx.size);
client.focus = Some(id.clone());
});
let msg_box = text let msg_box = text
.background(rect(Color::WHITE.darker(0.5))) .background(rect(Color::WHITE.darker(0.5)))
.add(&mut client.ui); .add(&mut client.ui);
@@ -213,11 +242,13 @@ impl Client {
Self { Self {
renderer, renderer,
window,
input: Input::default(), input: Input::default(),
ui, ui,
info, info,
focus: None, focus: None,
clipboard: Clipboard::new().unwrap(), clipboard: Clipboard::new().unwrap(),
ime: 0,
} }
} }
@@ -263,10 +294,31 @@ impl Client {
text.insert(&t); text.insert(&t);
} }
} }
TextInputResult::Copy(text) => {
if let Err(err) = self.clipboard.set_text(text) {
eprintln!("failed to copy text to clipboard: {err}")
}
}
TextInputResult::Unused | TextInputResult::Used => (), TextInputResult::Unused | TextInputResult::Used => (),
} }
} }
} }
WindowEvent::Ime(ime) => {
if let Some(sel) = &self.focus {
let mut text = self.ui.text(sel);
match ime {
Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real
text.replace(self.ime, &content);
self.ime = content.chars().count();
}
Ime::Commit(content) => {
text.insert(&content);
}
}
}
}
_ => (), _ => (),
} }
let new = format!( let new = format!(

View File

@@ -10,14 +10,14 @@ use crate::{
util::{HashMap, Id}, util::{HashMap, Id},
}; };
#[derive(PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum Button { pub enum Button {
Left, Left,
Right, Right,
Middle, Middle,
} }
#[derive(PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum CursorSense { pub enum CursorSense {
PressStart(Button), PressStart(Button),
Pressing(Button), Pressing(Button),
@@ -34,9 +34,15 @@ impl CursorSense {
pub fn click() -> Self { pub fn click() -> Self {
Self::PressStart(Button::Left) Self::PressStart(Button::Left)
} }
pub fn click_or_drag() -> CursorSenses {
Self::click() | Self::Pressing(Button::Left)
}
pub fn unclick() -> Self { pub fn unclick() -> Self {
Self::PressEnd(Button::Left) Self::PressEnd(Button::Left)
} }
pub fn is_dragging(&self) -> bool {
matches!(self, CursorSense::Pressing(Button::Left))
}
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@@ -108,6 +114,9 @@ pub struct CursorData {
pub cursor: Vec2, pub cursor: Vec2,
pub size: Vec2, pub size: Vec2,
pub scroll_delta: Vec2, pub scroll_delta: Vec2,
/// the (first) sense that triggered this event
/// the senses are checked in order
pub sense: CursorSense,
} }
pub struct CursorModule<Ctx> { pub struct CursorModule<Ctx> {
@@ -188,11 +197,12 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
sensed = true; sensed = true;
for sensor in &mut group.sensors { for sensor in &mut group.sensors {
if should_run(&sensor.senses, cursor, group.hover) { if let Some(sense) = should_run(&sensor.senses, cursor, group.hover) {
let data = CursorData { let data = CursorData {
cursor: cursor.pos - region.top_left, cursor: cursor.pos - region.top_left,
size: region.bot_right - region.top_left, size: region.bot_right - region.top_left,
scroll_delta: cursor.scroll_delta, scroll_delta: cursor.scroll_delta,
sense,
}; };
(sensor.f)(ctx, data); (sensor.f)(ctx, data);
} }
@@ -210,7 +220,11 @@ impl<Ctx: UiCtx + 'static> CursorModule<Ctx> {
} }
} }
pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: ActivationState) -> bool { pub fn should_run(
senses: &CursorSenses,
cursor: &CursorState,
hover: ActivationState,
) -> Option<CursorSense> {
for sense in senses.iter() { for sense in senses.iter() {
if match sense { if match sense {
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(), CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
@@ -221,10 +235,10 @@ pub fn should_run(senses: &CursorSenses, cursor: &CursorState, hover: Activation
CursorSense::HoverEnd => hover.is_end(), CursorSense::HoverEnd => hover.is_end(),
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO, CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
} { } {
return true; return Some(*sense);
} }
} }
false None
} }
impl ActivationState { impl ActivationState {
@@ -280,8 +294,8 @@ impl Event for CursorSense {
type Data = CursorData; type Data = CursorData;
} }
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static> EventModule<E, Ctx> impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static>
for CursorModule<Ctx> EventModule<E, Ctx> for CursorModule<Ctx>
{ {
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) { fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
// TODO: does not add to active if currently active // TODO: does not add to active if currently active

View File

@@ -94,7 +94,7 @@ impl TextBuilderOutput for TextEditOutput {
)); ));
let mut text = TextEdit { let mut text = TextEdit {
view: TextView::new(buf, builder.attrs, builder.hint.get(ui)), view: TextView::new(buf, builder.attrs, builder.hint.get(ui)),
cursor: None, selection: Default::default(),
}; };
let font_system = &mut ui.data.text.font_system; let font_system = &mut ui.data.text.font_system;
text.buf text.buf

View File

@@ -10,7 +10,7 @@ use winit::{
pub struct TextEdit { pub struct TextEdit {
pub(super) view: TextView, pub(super) view: TextView,
pub(super) cursor: Option<Cursor>, pub(super) selection: TextSelection,
} }
impl TextEdit { impl TextEdit {
@@ -21,23 +21,35 @@ impl TextEdit {
.align(self.align) .align(self.align)
} }
pub fn content(&self) -> String { pub fn select_content(&self, start: Cursor, end: Cursor) -> String {
self.buf let (start, end) = sort_cursors(start, end);
.lines let mut iter = self.buf.lines.iter().skip(start.line);
.iter() let first = iter.next().unwrap();
.map(|l| l.text()) if start.line == end.line {
.collect::<Vec<_>>() first.text()[start.index..end.index].to_string()
.join("\n") } else {
let mut str = first.text()[start.index..].to_string();
for _ in (start.line + 1)..end.line {
str = str + "\n" + iter.next().unwrap().text();
}
let last = iter.next().unwrap();
str = str + "\n" + &last.text()[..end.index];
str
}
} }
} }
impl Widget for TextEdit { impl Widget for TextEdit {
fn draw(&mut self, painter: &mut Painter) { fn draw(&mut self, painter: &mut Painter) {
let base = painter.layer;
painter.child_layer();
let region = self.view.draw(painter); let region = self.view.draw(painter);
painter.layer = base;
if let Some(cursor) = &self.cursor match &self.selection {
&& let Some(offset) = cursor_pos(cursor, &self.buf) TextSelection::None => (),
{ TextSelection::Pos(cursor) => {
if let Some(offset) = cursor_pos(cursor, &self.buf) {
let size = vec2(1, self.attrs.line_height); let size = vec2(1, self.attrs.line_height);
painter.primitive_within( painter.primitive_within(
RectPrimitive::color(Color::WHITE), RectPrimitive::color(Color::WHITE),
@@ -45,6 +57,52 @@ impl Widget for TextEdit {
); );
} }
} }
TextSelection::Span { start, end } => {
let (start, end) = sort_cursors(*start, *end);
let line_height = self.attrs.line_height;
let mut highlight = |offset, width: f32| {
painter.primitive_within(
RectPrimitive::color(Color::SKY),
vec2(width, line_height)
.align(Align::TOP_LEFT)
.offset(offset)
.within(&region),
);
};
if let (Some(start_offset), Some(end_offset)) =
(cursor_pos(&start, &self.buf), cursor_pos(&end, &self.buf))
{
let mut iter = self.buf.layout_runs().skip(start.line);
if start.line == end.line {
highlight(start_offset, end_offset.x - start_offset.x);
} else {
let first = iter.next().unwrap();
highlight(start_offset, first.line_w - start_offset.x);
for i in (start.line + 1)..end.line {
let line = iter.next().unwrap();
let offset = vec2(0.0, i as f32 * line_height);
highlight(offset, line.line_w);
}
let offset = vec2(0.0, end.line as f32 * line_height);
highlight(offset, end_offset.x);
}
// painter.primitive_within(
// RectPrimitive::color(Color::WHITE),
// size.align(Align::TOP_LEFT)
// .offset(end_offset)
// .within(&region),
// );
let size = vec2(1, self.attrs.line_height);
painter.primitive_within(
RectPrimitive::color(Color::WHITE),
size.align(Align::TOP_LEFT)
.offset(end_offset)
.within(&region),
);
}
}
}
}
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
self.view.desired_width(ctx) self.view.desired_width(ctx)
@@ -56,17 +114,17 @@ impl Widget for TextEdit {
} }
/// copied & modified from fn found in Editor in cosmic_text /// copied & modified from fn found in Editor in cosmic_text
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> { fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<Vec2> {
let mut prev = None; let mut prev = None;
for run in buf for run in buf
.layout_runs() .layout_runs()
.skip_while(|r| r.line_i < cursor.line) .skip_while(|r| r.line_i < cursor.line)
.take_while(|r| r.line_i == cursor.line) .take_while(|r| r.line_i == cursor.line)
{ {
prev = Some((run.line_w, run.line_top)); prev = Some(vec2(run.line_w, run.line_top));
for glyph in run.glyphs.iter() { for glyph in run.glyphs.iter() {
if cursor.index == glyph.start { if cursor.index == glyph.start {
return Some((glyph.x, run.line_top)); return Some(vec2(glyph.x, run.line_top));
} else if cursor.index > glyph.start && cursor.index < glyph.end { } else if cursor.index > glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters // Guess x offset based on characters
let mut before = 0; let mut before = 0;
@@ -81,7 +139,7 @@ fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
} }
let offset = glyph.w * (before as f32) / (total as f32); let offset = glyph.w * (before as f32) / (total as f32);
return Some((glyph.x + offset, run.line_top)); return Some(vec2(glyph.x + offset, run.line_top));
} }
} }
} }
@@ -106,22 +164,18 @@ impl<'a> TextEditCtx<'a> {
self.text self.text
.buf .buf
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None); .set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
if let Some(cursor) = &mut self.text.cursor { self.text.selection.clear();
cursor.line = 0;
cursor.index = 0;
cursor.affinity = Affinity::default();
}
text text
} }
pub fn motion(&mut self, motion: Motion) { pub fn motion(&mut self, motion: Motion) {
if let Some(cursor) = self.text.cursor if let TextSelection::Pos(cursor) = self.text.selection
&& let Some((cursor, _)) = && let Some((cursor, _)) =
self.text self.text
.buf .buf
.cursor_motion(self.font_system, cursor, None, motion) .cursor_motion(self.font_system, cursor, None, motion)
{ {
self.text.cursor = Some(cursor); self.text.selection = TextSelection::Pos(cursor);
} }
} }
@@ -144,12 +198,41 @@ impl<'a> TextEditCtx<'a> {
} }
} }
pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.text.selection {
let lines = &mut self.text.view.buf.lines;
let (start, end) = sort_cursors(start, end);
if start.line == end.line {
let line = &mut lines[start.line];
let text = line.text();
let text = text[..start.index].to_string() + &text[end.index..];
edit_line(line, text);
} else {
// start
let start_text = lines[start.line].text()[..start.index].to_string();
let end_text = &lines[end.line].text()[end.index..];
let text = start_text + end_text;
edit_line(&mut lines[start.line], text);
}
// between
let range = (start.line + 1)..=end.line;
if !range.is_empty() {
lines.splice(range, None);
}
self.text.selection = TextSelection::Pos(start);
true
} else {
false
}
}
fn insert_inner(&mut self, text: &str, mov: bool) { fn insert_inner(&mut self, text: &str, mov: bool) {
if let Some(cursor) = &mut self.text.cursor { self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let line = &mut self.text.view.buf.lines[cursor.line]; let line = &mut self.text.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string(); let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text); line_text.insert_str(cursor.index, text);
line.set_text(line_text, line.ending(), line.attrs_list().clone()); edit_line(line, line_text);
if mov { if mov {
for _ in 0..text.chars().count() { for _ in 0..text.chars().count() {
self.motion(Motion::Right); self.motion(Motion::Right);
@@ -159,7 +242,8 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn newline(&mut self) { pub fn newline(&mut self) {
if let Some(cursor) = &mut self.text.cursor { self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let lines = &mut self.text.view.buf.lines; let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line]; let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index); let new = line.split_off(cursor.index);
@@ -170,7 +254,8 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn backspace(&mut self) { pub fn backspace(&mut self) {
if let Some(cursor) = &mut self.text.cursor if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
&& (cursor.index != 0 || cursor.line != 0) && (cursor.index != 0 || cursor.line != 0)
{ {
self.motion(Motion::Left); self.motion(Motion::Left);
@@ -179,7 +264,9 @@ impl<'a> TextEditCtx<'a> {
} }
pub fn delete(&mut self) { pub fn delete(&mut self) {
if let Some(cursor) = &mut self.text.cursor { if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
{
let lines = &mut self.text.view.buf.lines; let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line]; let line = &mut lines[cursor.line];
if cursor.index == line.text().len() { if cursor.index == line.text().len() {
@@ -190,22 +277,47 @@ impl<'a> TextEditCtx<'a> {
let line = &mut lines[cursor.line]; let line = &mut lines[cursor.line];
let mut cur = line.text().to_string(); let mut cur = line.text().to_string();
cur.push_str(&add); cur.push_str(&add);
line.set_text(cur, line.ending(), line.attrs_list().clone()); edit_line(line, cur);
} else { } else {
let mut text = line.text().to_string(); let mut text = line.text().to_string();
text.remove(cursor.index); text.remove(cursor.index);
line.set_text(text, line.ending(), line.attrs_list().clone()); edit_line(line, text);
} }
} }
} }
pub fn select(&mut self, pos: Vec2, size: Vec2) { pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool) {
let pos = pos - self.text.region().top_left().to_abs(size); let pos = pos - self.text.region().top_left().to_abs(size);
self.text.cursor = self.text.buf.hit(pos.x, pos.y); let hit = self.text.buf.hit(pos.x, pos.y);
let sel = &mut self.text.selection;
match sel {
TextSelection::None => {
if !drag && let Some(hit) = hit {
*sel = TextSelection::Pos(hit)
}
}
TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => (),
(Some(hit), false) => *pos = hit,
(Some(end), true) => *sel = TextSelection::Span { start: *pos, end },
},
TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start),
(Some(hit), false) => *sel = TextSelection::Pos(hit),
(Some(hit), true) => *end = hit,
},
}
if let TextSelection::Span { start, end } = sel
&& start == end
{
*sel = TextSelection::Pos(*start);
}
} }
pub fn deselect(&mut self) { pub fn deselect(&mut self) {
self.text.cursor = None; self.text.selection = TextSelection::None;
} }
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
@@ -232,11 +344,20 @@ impl<'a> TextEditCtx<'a> {
_ => return TextInputResult::Unused, _ => return TextInputResult::Unused,
}, },
Key::Character(text) => { Key::Character(text) => {
if modifiers.control && text == "v" { if modifiers.control {
return TextInputResult::Paste; match text.as_str() {
} else { "v" => return TextInputResult::Paste,
self.insert(text) "c" => {
if let TextSelection::Span { start, end } = self.text.selection {
let content = self.text.select_content(start, end);
return TextInputResult::Copy(content);
} }
return TextInputResult::Used;
}
_ => (),
}
}
self.insert(text)
} }
_ => return TextInputResult::Unused, _ => return TextInputResult::Unused,
} }
@@ -262,9 +383,37 @@ pub enum TextInputResult {
Unused, Unused,
Unfocus, Unfocus,
Submit, Submit,
Copy(String),
Paste, Paste,
} }
#[derive(Default)]
pub enum TextSelection {
#[default]
None,
Pos(Cursor),
Span {
start: Cursor,
end: Cursor,
},
}
impl TextSelection {
pub fn clear(&mut self) {
match self {
TextSelection::None => (),
TextSelection::Pos(cursor) => {
cursor.line = 0;
cursor.index = 0;
cursor.affinity = Affinity::default();
}
TextSelection::Span { start: _, end: _ } => {
*self = TextSelection::None;
}
}
}
}
impl TextInputResult { impl TextInputResult {
pub fn unfocus(&self) -> bool { pub fn unfocus(&self) -> bool {
matches!(self, TextInputResult::Unfocus) matches!(self, TextInputResult::Unfocus)

View File

@@ -5,7 +5,7 @@ pub use build::*;
pub use edit::*; pub use edit::*;
use crate::{prelude::*, util::MutDetect}; use crate::{prelude::*, util::MutDetect};
use cosmic_text::{Attrs, Metrics, Shaping}; use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
pub const SHAPING: Shaping = Shaping::Advanced; pub const SHAPING: Shaping = Shaping::Advanced;
@@ -82,6 +82,15 @@ impl TextView {
painter.texture_within(&tex.handle, region); painter.texture_within(&tex.handle, region);
region region
} }
pub fn content(&self) -> String {
self.buf
.lines
.iter()
.map(|l| l.text())
.collect::<Vec<_>>()
.join("\n")
}
} }
impl Text { impl Text {
@@ -124,6 +133,16 @@ impl Widget for Text {
} }
} }
pub fn sort_cursors(a: Cursor, b: Cursor) -> (Cursor, Cursor) {
let start = a.min(b);
let end = a.max(b);
(start, end)
}
pub fn edit_line(line: &mut BufferLine, text: String) {
line.set_text(text, line.ending(), line.attrs_list().clone());
}
impl Deref for Text { impl Deref for Text {
type Target = TextAttrs; type Target = TextAttrs;

20
src/layout/attr.rs Normal file
View File

@@ -0,0 +1,20 @@
use crate::layout::{Ui, WidgetId, WidgetIdFn, WidgetLike};
pub trait WidgetAttr<W> {
type Input;
fn run(ui: &mut Ui, id: &WidgetId<W>, input: Self::Input);
}
pub trait Attrable<W, Tag> {
fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>;
}
impl<WL: WidgetLike<Tag>, Tag> Attrable<WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> {
|ui| {
let id = self.add(ui);
A::run(ui, &id, input);
id
}
}
}

View File

@@ -55,10 +55,7 @@ impl<W: WidgetLike<Tag>, Tag> Eventable<W::Widget, Tag> for W {
) -> impl WidgetIdFn<W::Widget> { ) -> impl WidgetIdFn<W::Widget> {
move |ui| { move |ui| {
let id = self.add(ui); let id = self.add(ui);
ui.data ui.register_event(&id, event, f);
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id, event, f);
id id
} }
} }

View File

@@ -9,6 +9,7 @@ mod painter;
mod text; mod text;
mod texture; mod texture;
mod ui; mod ui;
mod attr;
mod vec2; mod vec2;
mod widget; mod widget;
mod widgets; mod widgets;
@@ -24,6 +25,7 @@ pub use painter::*;
pub use text::*; pub use text::*;
pub use texture::*; pub use texture::*;
pub use ui::*; pub use ui::*;
pub use attr::*;
pub use vec2::*; pub use vec2::*;
pub use widget::*; pub use widget::*;
pub use widgets::*; pub use widgets::*;

View File

@@ -17,8 +17,6 @@ pub struct Painter<'a, 'c> {
children: Vec<Id>, children: Vec<Id>,
children_width: HashMap<Id, (UiVec2, Len)>, children_width: HashMap<Id, (UiVec2, Len)>,
children_height: HashMap<Id, (UiVec2, Len)>, children_height: HashMap<Id, (UiVec2, Len)>,
/// whether this widget depends on region's final pixel size or not
/// TODO: decide if point (pt) should be used here instead of px
pub layer: usize, pub layer: usize,
id: Id, id: Id,
} }

View File

@@ -3,8 +3,8 @@ use image::DynamicImage;
use crate::{ use crate::{
core::{TextEdit, TextEditCtx}, core::{TextEdit, TextEditCtx},
layout::{ layout::{
IdLike, PainterData, PixelRegion, StaticWidgetId, TextureHandle, Vec2, Widget, Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, StaticWidgetId,
WidgetId, WidgetInstance, WidgetLike, TextureHandle, Vec2, Widget, WidgetId, WidgetInstance, WidgetLike,
}, },
util::{HashSet, Id}, util::{HashSet, Id},
}; };
@@ -96,6 +96,18 @@ impl Ui {
self.data.textures.add(image) self.data.textures.add(image)
} }
pub fn register_event<W, E: Event, Ctx: 'static>(
&mut self,
id: &WidgetId<W>,
event: E,
f: impl EventFn<Ctx, E::Data>,
) {
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id, event, f);
}
pub fn resize(&mut self, size: impl Into<Vec2>) { pub fn resize(&mut self, size: impl Into<Vec2>) {
self.data.output_size = size.into(); self.data.output_size = size.into();
self.resized = true; self.resized = true;