@@ -7,6 +7,7 @@ use blitz_traits::{
77 } ,
88 navigation:: NavigationOptions ,
99} ;
10+ use keyboard_types:: Modifiers ;
1011use markup5ever:: local_name;
1112
1213use crate :: { BaseDocument , node:: SpecialElementData } ;
@@ -79,7 +80,13 @@ pub(crate) fn handle_mousemove<F: FnMut(DomEvent)>(
7980 changed
8081}
8182
82- pub ( crate ) fn handle_mousedown ( doc : & mut BaseDocument , target : usize , x : f32 , y : f32 ) {
83+ pub ( crate ) fn handle_mousedown (
84+ doc : & mut BaseDocument ,
85+ target : usize ,
86+ x : f32 ,
87+ y : f32 ,
88+ mods : Modifiers ,
89+ ) {
8390 let Some ( hit) = doc. hit ( x, y) else {
8491 return ;
8592 } ;
@@ -114,11 +121,36 @@ pub(crate) fn handle_mousedown(doc: &mut BaseDocument, target: usize, x: f32, y:
114121 let x = ( hit. x - content_box_offset. x ) as f64 * doc. viewport . scale_f64 ( ) ;
115122 let y = ( hit. y - content_box_offset. y ) as f64 * doc. viewport . scale_f64 ( ) ;
116123
117- text_input_data
124+ // TODO: Only increment click count if click maps to the same/similar caret position as the previous click
125+ let click_count = if doc
126+ . last_click_time
127+ . map ( |t| t. elapsed ( ) < Duration :: from_millis ( 500 ) )
128+ . unwrap_or ( false )
129+ {
130+ // Add 1 to doc.click_count because the click count hasn't yet been updated in the mousedown event
131+ doc. click_count + 1
132+ } else {
133+ 1
134+ } ;
135+
136+ let mut font_ctx = doc. font_ctx . lock ( ) . unwrap ( ) ;
137+ let mut driver = text_input_data
118138 . editor
119- . driver ( & mut doc. font_ctx . lock ( ) . unwrap ( ) , & mut doc. layout_ctx )
120- . move_to_point ( x as f32 , y as f32 ) ;
139+ . driver ( & mut font_ctx, & mut doc. layout_ctx ) ;
121140
141+ match click_count {
142+ 1 => {
143+ if mods. shift ( ) {
144+ driver. shift_click_extension ( x as f32 , y as f32 ) ;
145+ } else {
146+ driver. move_to_point ( x as f32 , y as f32 ) ;
147+ }
148+ }
149+ 2 => driver. select_word_at_point ( x as f32 , y as f32 ) ,
150+ _ => driver. select_hard_line_at_point ( x as f32 , y as f32 ) ,
151+ }
152+
153+ drop ( font_ctx) ;
122154 doc. set_focus_to ( hit. node_id ) ;
123155 }
124156}
@@ -164,136 +196,147 @@ pub(crate) fn handle_mouseup<F: FnMut(DomEvent)>(
164196 }
165197}
166198
167- pub ( crate ) fn handle_click < F : FnMut ( DomEvent ) > (
199+ pub ( crate ) fn handle_click (
168200 doc : & mut BaseDocument ,
169201 target : usize ,
170202 event : & BlitzMouseButtonEvent ,
171- mut dispatch_event : F ,
203+ dispatch_event : & mut dyn FnMut ( DomEvent ) ,
172204) {
173- let mut maybe_node_id = Some ( target) ;
174- while let Some ( node_id) = maybe_node_id {
175- let maybe_element = {
176- let node = & mut doc. nodes [ node_id] ;
177- node. data . downcast_element_mut ( )
178- } ;
205+ let double_click_event = event. clone ( ) ;
179206
180- let Some ( el) = maybe_element else {
181- maybe_node_id = doc. nodes [ node_id] . parent ;
182- continue ;
183- } ;
184-
185- let disabled = el. attr ( local_name ! ( "disabled" ) ) . is_some ( ) ;
186- if disabled {
187- return ;
188- }
189-
190- if let SpecialElementData :: TextInput ( _) = el. special_data {
191- return ;
192- }
207+ let mut maybe_node_id = Some ( target) ;
208+ let matched = ' matched: {
209+ while let Some ( node_id) = maybe_node_id {
210+ let maybe_element = {
211+ let node = & mut doc. nodes [ node_id] ;
212+ node. data . downcast_element_mut ( )
213+ } ;
214+
215+ let Some ( el) = maybe_element else {
216+ maybe_node_id = doc. nodes [ node_id] . parent ;
217+ continue ;
218+ } ;
219+
220+ let disabled = el. attr ( local_name ! ( "disabled" ) ) . is_some ( ) ;
221+ if disabled {
222+ break ' matched true ;
223+ }
193224
194- match el. name . local {
195- local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "checkbox" ) => {
196- let is_checked = BaseDocument :: toggle_checkbox ( el) ;
197- let value = is_checked. to_string ( ) ;
198- dispatch_event ( DomEvent :: new (
199- node_id,
200- DomEventData :: Input ( BlitzInputEvent { value } ) ,
201- ) ) ;
202- doc. set_focus_to ( node_id) ;
203- return ;
225+ if let SpecialElementData :: TextInput ( _) = el. special_data {
226+ break ' matched true ;
204227 }
205- local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "radio" ) => {
206- let radio_set = el. attr ( local_name ! ( "name" ) ) . unwrap ( ) . to_string ( ) ;
207- BaseDocument :: toggle_radio ( doc, radio_set, node_id) ;
208228
209- // TODO: make input event conditional on value actually changing
210- let value = String :: from ( "true" ) ;
211- dispatch_event ( DomEvent :: new (
212- node_id,
213- DomEventData :: Input ( BlitzInputEvent { value } ) ,
214- ) ) ;
229+ match el. name . local {
230+ local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "checkbox" ) => {
231+ let is_checked = BaseDocument :: toggle_checkbox ( el) ;
232+ let value = is_checked. to_string ( ) ;
233+ dispatch_event ( DomEvent :: new (
234+ node_id,
235+ DomEventData :: Input ( BlitzInputEvent { value } ) ,
236+ ) ) ;
237+ doc. set_focus_to ( node_id) ;
238+ break ' matched true ;
239+ }
240+ local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "radio" ) => {
241+ let radio_set = el. attr ( local_name ! ( "name" ) ) . unwrap ( ) . to_string ( ) ;
242+ BaseDocument :: toggle_radio ( doc, radio_set, node_id) ;
243+
244+ // TODO: make input event conditional on value actually changing
245+ let value = String :: from ( "true" ) ;
246+ dispatch_event ( DomEvent :: new (
247+ node_id,
248+ DomEventData :: Input ( BlitzInputEvent { value } ) ,
249+ ) ) ;
215250
216- BaseDocument :: set_focus_to ( doc, node_id) ;
251+ BaseDocument :: set_focus_to ( doc, node_id) ;
217252
218- return ;
219- }
220- // Clicking labels triggers click, and possibly input event, of associated input
221- local_name ! ( "label" ) => {
222- if let Some ( target_node_id) = doc. label_bound_input_element ( node_id) . map ( |n| n. id ) {
223- // Apply default click event action for target node
224- let target_node = doc. get_node_mut ( target_node_id) . unwrap ( ) ;
225- let syn_event = target_node. synthetic_click_event_data ( event. mods ) ;
226- handle_click ( doc, target_node_id, & syn_event, dispatch_event) ;
227- return ;
253+ break ' matched true ;
228254 }
229- }
230- local_name ! ( "a" ) => {
231- if let Some ( href) = el. attr ( local_name ! ( "href" ) ) {
232- if let Some ( url) = doc. url . resolve_relative ( href) {
233- doc. navigation_provider . navigate_to ( NavigationOptions :: new (
234- url,
235- String :: from ( "text/plain" ) ,
236- doc. id ( ) ,
237- ) ) ;
238- } else {
239- println ! ( "{href} is not parseable as a url. : {:?}" , * doc. url)
255+ // Clicking labels triggers click, and possibly input event, of associated input
256+ local_name ! ( "label" ) => {
257+ if let Some ( target_node_id) =
258+ doc. label_bound_input_element ( node_id) . map ( |n| n. id )
259+ {
260+ // Apply default click event action for target node
261+ let target_node = doc. get_node_mut ( target_node_id) . unwrap ( ) ;
262+ let syn_event = target_node. synthetic_click_event_data ( event. mods ) ;
263+ handle_click ( doc, target_node_id, & syn_event, dispatch_event) ;
264+ break ' matched true ;
240265 }
241- return ;
242- } else {
243- println ! ( "Clicked link without href: {:?}" , el. attrs( ) ) ;
244266 }
245- }
246- local_name ! ( "input" )
247- if el. is_submit_button ( ) || el. attr ( local_name ! ( "type" ) ) == Some ( "submit" ) =>
248- {
249- if let Some ( form_owner) = doc. controls_to_form . get ( & node_id) {
250- doc. submit_form ( * form_owner, node_id) ;
267+ local_name ! ( "a" ) => {
268+ if let Some ( href) = el. attr ( local_name ! ( "href" ) ) {
269+ if let Some ( url) = doc. url . resolve_relative ( href) {
270+ doc. navigation_provider . navigate_to ( NavigationOptions :: new (
271+ url,
272+ String :: from ( "text/plain" ) ,
273+ doc. id ( ) ,
274+ ) ) ;
275+ } else {
276+ println ! ( "{href} is not parseable as a url. : {:?}" , * doc. url)
277+ }
278+ break ' matched true ;
279+ } else {
280+ println ! ( "Clicked link without href: {:?}" , el. attrs( ) ) ;
281+ }
251282 }
252- }
253- #[ cfg( feature = "file_input" ) ]
254- local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "file" ) => {
255- use crate :: qual_name;
256- //TODO: Handle accept attribute https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept by passing an appropriate filter
257- let multiple = el. attr ( local_name ! ( "multiple" ) ) . is_some ( ) ;
258- let files = doc. shell_provider . open_file_dialog ( multiple, None ) ;
259-
260- if let Some ( file) = files. first ( ) {
261- el. attrs
262- . set ( qual_name ! ( "value" , html) , & file. to_string_lossy ( ) ) ;
283+ local_name ! ( "input" )
284+ if el. is_submit_button ( ) || el. attr ( local_name ! ( "type" ) ) == Some ( "submit" ) =>
285+ {
286+ if let Some ( form_owner) = doc. controls_to_form . get ( & node_id) {
287+ doc. submit_form ( * form_owner, node_id) ;
288+ }
263289 }
264- let text_content = match files. len ( ) {
265- 0 => "No Files Selected" . to_string ( ) ,
266- 1 => files
267- . first ( )
268- . unwrap ( )
269- . file_name ( )
270- . unwrap_or_default ( )
271- . to_string_lossy ( )
272- . to_string ( ) ,
273- x => format ! ( "{x} Files Selected" ) ,
274- } ;
275-
276- if files. is_empty ( ) {
277- el. special_data = SpecialElementData :: None ;
278- } else {
279- el. special_data = SpecialElementData :: FileInput ( files. into ( ) )
290+ #[ cfg( feature = "file_input" ) ]
291+ local_name ! ( "input" ) if el. attr ( local_name ! ( "type" ) ) == Some ( "file" ) => {
292+ use crate :: qual_name;
293+ //TODO: Handle accept attribute https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/accept by passing an appropriate filter
294+ let multiple = el. attr ( local_name ! ( "multiple" ) ) . is_some ( ) ;
295+ let files = doc. shell_provider . open_file_dialog ( multiple, None ) ;
296+
297+ if let Some ( file) = files. first ( ) {
298+ el. attrs
299+ . set ( qual_name ! ( "value" , html) , & file. to_string_lossy ( ) ) ;
300+ }
301+ let text_content = match files. len ( ) {
302+ 0 => "No Files Selected" . to_string ( ) ,
303+ 1 => files
304+ . first ( )
305+ . unwrap ( )
306+ . file_name ( )
307+ . unwrap_or_default ( )
308+ . to_string_lossy ( )
309+ . to_string ( ) ,
310+ x => format ! ( "{x} Files Selected" ) ,
311+ } ;
312+
313+ if files. is_empty ( ) {
314+ el. special_data = SpecialElementData :: None ;
315+ } else {
316+ el. special_data = SpecialElementData :: FileInput ( files. into ( ) )
317+ }
318+ let child_label_id = doc. nodes [ node_id] . children [ 1 ] ;
319+ let child_text_id = doc. nodes [ child_label_id] . children [ 0 ] ;
320+ let text_data = doc. nodes [ child_text_id]
321+ . text_data_mut ( )
322+ . expect ( "Text data not found" ) ;
323+ text_data. content = text_content;
280324 }
281- let child_label_id = doc. nodes [ node_id] . children [ 1 ] ;
282- let child_text_id = doc. nodes [ child_label_id] . children [ 0 ] ;
283- let text_data = doc. nodes [ child_text_id]
284- . text_data_mut ( )
285- . expect ( "Text data not found" ) ;
286- text_data. content = text_content;
325+ _ => { }
287326 }
288- _ => { }
327+
328+ // No match. Recurse up to parent.
329+ maybe_node_id = doc. nodes [ node_id] . parent ;
289330 }
290331
291- // No match. Recurse up to parent.
292- maybe_node_id = doc . nodes [ node_id ] . parent ;
293- }
332+ // Didn't match anything
333+ false
334+ } ;
294335
295336 // If nothing is matched then clear focus
296- doc. clear_focus ( ) ;
337+ if !matched {
338+ doc. clear_focus ( ) ;
339+ }
297340
298341 // Assumed double click time to be less than 500ms, although may be system-dependant?
299342 if doc
@@ -307,7 +350,7 @@ pub(crate) fn handle_click<F: FnMut(DomEvent)>(
307350 if doc. click_count == 2 {
308351 dispatch_event ( DomEvent :: new (
309352 target,
310- DomEventData :: DoubleClick ( event . clone ( ) ) ,
353+ DomEventData :: DoubleClick ( double_click_event ) ,
311354 ) ) ;
312355 }
313356 } else {
0 commit comments