Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
aa08bad
Add clipboard event types for copy, cut, and paste
niminypiminy Mar 12, 2026
9c4eb22
Refactor keyboard input handling for shortcuts
niminypiminy Mar 12, 2026
ee52756
Handle ClipboardPaste event in driver.rs
niminypiminy Mar 12, 2026
8b57aeb
Add Paste event and reorder event handling
niminypiminy Mar 12, 2026
27e0b42
Update driver.rs
niminypiminy Mar 12, 2026
786c168
Update mod.rs
niminypiminy Mar 12, 2026
f9bc0cf
Update dioxus_document.rs
niminypiminy Mar 12, 2026
db74214
Update window.rs
niminypiminy Mar 12, 2026
b0de6d7
Update events.rs
niminypiminy Mar 12, 2026
a5c5904
Update events.rs
niminypiminy Mar 12, 2026
d815164
Update driver.rs
niminypiminy Mar 12, 2026
21afa56
Update driver.rs
niminypiminy Mar 12, 2026
b1c387f
Update events.rs
niminypiminy Mar 12, 2026
8ff6ce3
Update window.rs
niminypiminy Mar 12, 2026
8e61b1f
Update events.rs
niminypiminy Mar 12, 2026
49aa523
Update events.rs
niminypiminy Mar 12, 2026
8bfb4f8
Update dioxus_document.rs
niminypiminy Mar 12, 2026
61d7e37
Update events.rs
niminypiminy Mar 12, 2026
5054447
Update events.rs
niminypiminy Mar 13, 2026
68645c5
Update window.rs
niminypiminy Mar 13, 2026
7d6cae6
Update window.rs
niminypiminy Mar 13, 2026
ab4d010
Update window.rs
niminypiminy Mar 13, 2026
7861b9a
Update window.rs
niminypiminy Mar 13, 2026
b706f81
Update window.rs
niminypiminy Mar 13, 2026
4fd0f98
Update window.rs
niminypiminy Mar 13, 2026
64ae893
Update events.rs
niminypiminy Mar 13, 2026
2d61ef8
Update events.rs
niminypiminy Mar 13, 2026
51b5c5a
Update window.rs
niminypiminy Mar 13, 2026
5c99a83
Update window.rs
niminypiminy Mar 13, 2026
330e4d8
Update window.rs
niminypiminy Mar 13, 2026
86e7df4
Update Cargo.toml
niminypiminy Mar 13, 2026
546aaaf
Update window.rs
niminypiminy Mar 13, 2026
cd43173
Update window.rs
niminypiminy Mar 13, 2026
bc6a4ae
Update window.rs
niminypiminy Mar 13, 2026
45b10ea
Update window.rs
niminypiminy Mar 13, 2026
01897dc
Update dioxus_document.rs
niminypiminy Mar 13, 2026
6712bdb
Update window.rs
niminypiminy Mar 13, 2026
df24f33
Update window.rs
niminypiminy Mar 13, 2026
39988c3
Update window.rs
niminypiminy Mar 13, 2026
e5ca923
Update window.rs
niminypiminy Mar 13, 2026
344d5a4
Update application.rs
niminypiminy Mar 13, 2026
164a0a1
failed to resolve, winit no longer supports exit. deleting function
niminypiminy Mar 13, 2026
c2b89d4
Update window.rs
niminypiminy Mar 13, 2026
0318215
update application.rs shutdown
niminypiminy Mar 13, 2026
bc28770
Merge pull request #1 from niminypiminy/fix-segfault-linux
niminypiminy Mar 13, 2026
152e93b
Update application.rs
niminypiminy Mar 13, 2026
b2a9417
Update Cargo.toml
niminypiminy Mar 13, 2026
855dda3
Update window.rs
niminypiminy Mar 16, 2026
b972797
Update window.rs
niminypiminy Mar 16, 2026
24f44e5
Merge pull request #2 from niminypiminy/clippy-fix
niminypiminy Mar 17, 2026
f49f94c
Add files via upload
niminypiminy Mar 17, 2026
94d8955
Add files via upload
niminypiminy Mar 17, 2026
eadd7e6
Add files via upload
niminypiminy Mar 17, 2026
2a83c01
Add files via upload
niminypiminy Mar 17, 2026
096c77f
Update element.rs
niminypiminy Mar 17, 2026
8f13321
Update form.rs
niminypiminy Mar 17, 2026
cd16149
Update element.rs
niminypiminy Mar 17, 2026
bfe5d95
Update form.rs
niminypiminy Mar 17, 2026
cbf9bf9
Update form.rs
niminypiminy Mar 17, 2026
3d8ede7
Update form.rs
niminypiminy Mar 17, 2026
5fab032
Update keyboard.rs
niminypiminy Mar 17, 2026
1c89909
Update keyboard.rs
niminypiminy Mar 17, 2026
4d43878
Update keyboard.rs
niminypiminy Mar 17, 2026
cdfebf3
Update keyboard.rs
niminypiminy Mar 17, 2026
b6d13c2
Merge pull request #3 from niminypiminy/password_type
niminypiminy Mar 17, 2026
88d098b
Update application.rs
niminypiminy Mar 17, 2026
f1eb9f2
Update window.rs
niminypiminy Mar 17, 2026
358a88a
Update application.rs
niminypiminy Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/blitz-dom/src/events/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> {
UiEvent::KeyUp(_) => focussed_node_id,
UiEvent::KeyDown(_) => focussed_node_id,
UiEvent::Ime(_) => focussed_node_id,
UiEvent::ClipboardPaste(_) => focussed_node_id,
UiEvent::ClipboardCopy | UiEvent::ClipboardCut => focussed_node_id,
};
let target = target.unwrap_or_else(|| self.doc.inner().root_element().id);

Expand Down Expand Up @@ -222,6 +224,15 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> {
UiEvent::Ime(data) => {
self.handle_dom_event(DomEvent::new(target, DomEventData::Ime(data)))
}
UiEvent::ClipboardPaste(data) => {
self.handle_dom_event(DomEvent::new(target, DomEventData::Paste(data)))
}
UiEvent::ClipboardCopy => {
self.handle_dom_event(DomEvent::new(target, DomEventData::Copy))
}
UiEvent::ClipboardCut => {
self.handle_dom_event(DomEvent::new(target, DomEventData::Cut))
}
};

// Update document input state (hover, focus, active, etc)
Expand Down
43 changes: 33 additions & 10 deletions packages/blitz-dom/src/events/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ pub(crate) fn handle_keypress<F: FnMut(DomEvent)>(
if let Some(generated_event) = generated_event {
match generated_event {
GeneratedEvent::Input => {
let value = input_data.editor.raw_text().to_string();
let value = if input_data.is_password {
input_data.shadow_text.clone()
} else {
input_data.editor.raw_text().to_string()
};
dispatch_event(DomEvent::new(
node_id,
DomEventData::Input(BlitzInputEvent { value }),
Expand Down Expand Up @@ -141,14 +145,7 @@ fn apply_keypress_event(

return Some(GeneratedEvent::Input);
}
Key::Character(c) if action_mod && matches!(c.to_lowercase().as_str(), "a") => {
if shift {
driver.collapse_selection()
} else {
driver.select_all()
}
return Some(GeneratedEvent::Select);
}

Key::ArrowLeft => {
if action_mod {
if shift {
Expand Down Expand Up @@ -222,6 +219,9 @@ fn apply_keypress_event(
return Some(GeneratedEvent::Select);
}
Key::Delete => {
if input_data.is_password {
sync_shadow_before_edit(&mut input_data.shadow_text, &driver.editor);
}
if action_mod {
driver.delete_word()
} else {
Expand All @@ -230,6 +230,9 @@ fn apply_keypress_event(
return Some(GeneratedEvent::Input);
}
Key::Backspace => {
if input_data.is_password {
sync_shadow_before_edit(&mut input_data.shadow_text, &driver.editor);
}
if action_mod {
driver.backdelete_word()
} else {
Expand All @@ -254,8 +257,17 @@ fn apply_keypress_event(
}
}
Key::Character(s) => {
if input_data.is_password {
let selection = driver.editor.raw_selection();
if selection.anchor() != selection.focus() {
input_data.shadow_text.clear();
}
input_data.shadow_text.push_str(&s);
driver.insert_or_replace_selection("•");
} else {
driver.insert_or_replace_selection(&s);
return Some(GeneratedEvent::Input);
}
return Some(GeneratedEvent::Input);
}
_ => {}
};
Expand Down Expand Up @@ -300,3 +312,14 @@ fn implicit_form_submission(doc: &BaseDocument, text_target: usize) {

doc.submit_form(*form_owner_id, *form_owner_id);
}

fn sync_shadow_before_edit(shadow_text: &mut String, editor: &parley::PlainEditor<TextBrush>) {
let selection = editor.raw_selection();
if selection.anchor() == selection.focus() {
if !shadow_text.is_empty() {
shadow_text.pop();
}
} else {
shadow_text.clear();
}
}
5 changes: 5 additions & 0 deletions packages/blitz-dom/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ pub(crate) fn handle_dom_event<F: FnMut(DomEvent)>(
DomEventData::Blur(_) => None,
DomEventData::FocusIn(_) => None,
DomEventData::FocusOut(_) => None,

DomEventData::Paste(data) => Some(UiEvent::ClipboardPaste(data)),
DomEventData::Copy => Some(UiEvent::ClipboardCopy),
DomEventData::Cut => Some(UiEvent::ClipboardCut)
};

if let Some(ui_event) = ui_event {
Expand Down Expand Up @@ -202,5 +206,6 @@ pub(crate) fn handle_dom_event<F: FnMut(DomEvent)>(
DomEventData::FocusOut(_) => {
// Do nothing (no default action)
}
DomEventData::Paste(_) | DomEventData::Copy | DomEventData::Cut => {}
}
}
11 changes: 7 additions & 4 deletions packages/blitz-dom/src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,13 @@ fn construct_entry_list(doc: &BaseDocument, form_id: usize, submitter_id: usize)
create_entry(name, charset.into());
}
// Otherwise, create an entry with name and the value of the field element, and append it to entry list.
else if let Some(text) = element.text_input_data() {
create_entry(name, text.editor.text().to_string().as_str().into());
} else if let Some(value) = element.attr(local_name!("value")) {
create_entry(name, value.into());
else if let Some(text) = element.text_input_data() {
let value = if text.is_password {
text.shadow_text.clone()
} else {
text.editor.text().to_string()
};
create_entry(name, value.as_str().into());
}
}
entry_list
Expand Down
15 changes: 14 additions & 1 deletion packages/blitz-dom/src/layout/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,21 @@ fn create_text_editor(doc: &mut BaseDocument, input_element_id: usize, is_multil
let element = &mut node.data.downcast_element_mut().unwrap();
if !matches!(element.special_data, SpecialElementData::TextInput(_)) {
let mut text_input_data = TextInputData::new(is_multiline);

if element.attr(local_name!("type")) == Some("password") {
text_input_data.is_password = true;
}
let editor = &mut text_input_data.editor;
editor.set_text(element.attr(local_name!("value")).unwrap_or(" "));

// logic for the masking...
let initial_text = element.attr(local_name!("value")).unwrap_or("");
if text_input_data.is_password {
text_input_data.shadow_text = initial_text.to_string();
editor.set_text(&"•".repeat(initial_text.chars().count()));
} else {
editor.set_text(initial_text);
}

element.special_data = SpecialElementData::TextInput(text_input_data);
}

Expand Down
31 changes: 18 additions & 13 deletions packages/blitz-dom/src/node/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,26 +490,22 @@ impl BackgroundImageData {
}
}

#[derive(Clone)]
pub struct TextInputData {
/// A parley TextEditor instance
pub editor: Box<parley::PlainEditor<TextBrush>>,
/// Whether the input is a singleline or multiline input
pub is_multiline: bool,
}

// FIXME: Implement Clone for PlainEditor
impl Clone for TextInputData {
fn clone(&self) -> Self {
TextInputData::new(self.is_multiline)
}
pub is_password: bool,
// this is plaintext string
pub shadow_text: String,
}

impl TextInputData {
pub fn new(is_multiline: bool) -> Self {
let editor = Box::new(parley::PlainEditor::new(16.0));
Self {
editor,
editor: Box::new(parley::PlainEditor::new(16.0)),
is_multiline,
is_password: false,
shadow_text: String::new(),
}
}

Expand All @@ -519,8 +515,17 @@ impl TextInputData {
layout_ctx: &mut LayoutContext<TextBrush>,
text: &str,
) {
if self.editor.text() != text {
self.editor.set_text(text);
self.shadow_text = text.to_string();

// show password
let display_text = if self.is_password {
"•".repeat(text.chars().count())
} else {
text.to_string()
};

if self.editor.text() != display_text.as_str() {
self.editor.set_text(&display_text);
self.editor.driver(font_ctx, layout_ctx).refresh_layout();
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/blitz-shell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ data-url = { workspace = true, optional = true }
[target.'cfg(target_os = "android")'.dependencies]
android-activity = { version = "0.6.0" }

[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
arboard = { workspace = true, optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
arboard = { workspace = true, optional = true, features = ["wayland-data-control"] }

# for other platforms
[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
arboard = { workspace = true, optional = true }

[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
rfd = { workspace = true, optional = true, features = ["xdg-portal"] }
Expand Down
2 changes: 1 addition & 1 deletion packages/blitz-shell/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl<Rend: WindowRenderer> ApplicationHandler for BlitzApplication<Rend> {
// TODO
}

fn window_event(
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
Expand Down
90 changes: 45 additions & 45 deletions packages/blitz-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ impl<Rend: WindowRenderer> WindowConfig<Rend> {
}

pub struct View<Rend: WindowRenderer> {

//if we move this to the top it should be correct order. allowing surface to die.
pub window: Arc<dyn Window>,

pub doc: Box<dyn Document>,

pub renderer: Rend,
pub waker: Option<Waker>,

pub proxy: BlitzShellProxy,
pub window: Arc<dyn Window>,

/// The state of the keyboard modifiers (ctrl, shift, etc). Winit/Tao don't track these for us so we
/// need to store them in order to have access to them when processing keypress events
Expand Down Expand Up @@ -388,63 +391,60 @@ impl<Rend: WindowRenderer> View<Rend> {
self.request_redraw();
},
WindowEvent::ModifiersChanged(new_state) => {
// Store new keyboard modifier (ctrl, shift, etc) state for later use
self.keyboard_modifiers = new_state;
}
WindowEvent::KeyboardInput { event, .. } => {

if let PhysicalKey::Code(key_code) = event.physical_key && event.state.is_pressed() {
let ctrl = self.keyboard_modifiers.state().control_key();
let meta = self.keyboard_modifiers.state().meta_key();
let alt = self.keyboard_modifiers.state().alt_key();

// Ctrl/Super keyboard shortcuts
if ctrl | meta {
match key_code {
KeyCode::Equal => {
self.doc.inner_mut().viewport_mut().zoom_by(0.1);
},
KeyCode::Minus => {
self.doc.inner_mut().viewport_mut().zoom_by(-0.1);
},
KeyCode::Digit0 => {
self.doc.inner_mut().viewport_mut().set_zoom(1.0);
let modifiers = self.keyboard_modifiers.state();
let is_pressed = event.state.is_pressed();

if let PhysicalKey::Code(key_code) = event.physical_key && is_pressed {
let ctrl = modifiers.control_key();
let meta = modifiers.meta_key();
let alt = modifiers.alt_key();

if ctrl | meta {
match key_code {
KeyCode::Equal => self.doc.inner_mut().viewport_mut().zoom_by(0.1),
KeyCode::Minus => self.doc.inner_mut().viewport_mut().zoom_by(-0.1),
KeyCode::Digit0 => self.doc.inner_mut().viewport_mut().set_zoom(1.0),
KeyCode::KeyV => {
let shell = self.doc.inner().shell_provider.clone();
if let Ok(text) = shell.get_clipboard_text() {
let event = blitz_traits::events::BlitzClipboardEvent { content: text };
self.doc.handle_ui_event(UiEvent::ClipboardPaste(event));
}
_ => {}
};
}
KeyCode::KeyC => {
println!("DEBUG: Copy Triggered");
self.doc.handle_ui_event(UiEvent::ClipboardCopy);
}
KeyCode::KeyX => {
println!("DEBUG: Cut Triggered");
self.doc.handle_ui_event(UiEvent::ClipboardCut);
}
_ => {}
}
self.request_redraw();
}

// Alt keyboard shortcuts
if alt {
match key_code {
KeyCode::KeyD => {
let mut inner = self.doc.inner_mut();
inner.devtools_mut().toggle_show_layout();
drop(inner);
self.request_redraw();
}
KeyCode::KeyH => {
let mut inner = self.doc.inner_mut();
inner.devtools_mut().toggle_highlight_hover();
drop(inner);
self.request_redraw();
}
KeyCode::KeyT => self.doc.inner().print_taffy_tree(),
_ => {}
};
if alt {
match key_code {
KeyCode::KeyD => self.doc.inner_mut().devtools_mut().toggle_show_layout(),
KeyCode::KeyH => self.doc.inner_mut().devtools_mut().toggle_highlight_hover(),
KeyCode::KeyT => self.doc.inner().print_taffy_tree(),
_ => {}
}

self.request_redraw();
}
}

// Unmodified keypresses
let key_event_data = winit_key_event_to_blitz(&event, self.keyboard_modifiers.state());
let event = if event.state.is_pressed() {
let key_event_data = winit_key_event_to_blitz(&event, modifiers);
let ui_event = if is_pressed {
UiEvent::KeyDown(key_event_data)
} else {
UiEvent::KeyUp(key_event_data)
};

self.doc.handle_ui_event(event);
self.doc.handle_ui_event(ui_event);
}
WindowEvent::PointerEntered { /*device_id*/.. } => {}
WindowEvent::PointerLeft { /*device_id*/.. } => {}
Expand Down
Loading