mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-28 18:52:16 +00:00
lsp: Use a more "visual" element selection for Preview UI
Try a more visual selection approach over the rather technical tree-based one from earlier. In this commit, a click selects the element that was rendered at the click position *last*. I use rendered loosely here: It even takes elements into account that are invisible but still cover the clicked location. A double-click punshes through to the item rendered earlier (behind) the currently selected item that also covers the clicked position. A shift-double-click moves towards the later rendered elements again, usually undoing the last double-click.
This commit is contained in:
parent
e93b307643
commit
d50373951f
7 changed files with 142 additions and 152 deletions
|
|
@ -390,7 +390,7 @@ pub fn reset_selections(ui: &ui::PreviewUi) {
|
|||
|
||||
pub fn set_selections(
|
||||
ui: Option<&ui::PreviewUi>,
|
||||
element_position: Option<(&ElementRc, LogicalRect)>,
|
||||
element_position: Option<(&ElementRc, LogicalRect, usize)>,
|
||||
positions: ComponentPositions,
|
||||
) {
|
||||
let Some(ui) = ui else {
|
||||
|
|
@ -402,7 +402,7 @@ pub fn set_selections(
|
|||
positions.geometries.len() + if element_position.is_some() { 1 } else { 0 },
|
||||
);
|
||||
|
||||
if let Some((e, primary_position)) = element_position.as_ref() {
|
||||
if let Some((e, primary_position, _)) = element_position.as_ref() {
|
||||
let border_color = if e.borrow().layout.is_some() {
|
||||
i_slint_core::Color::from_argb_encoded(0xffff0000)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -63,74 +63,26 @@ pub fn unselect_element() {
|
|||
}
|
||||
|
||||
fn select_element(
|
||||
x: f32,
|
||||
y: f32,
|
||||
component_instance: &ComponentInstance,
|
||||
selected_element: Option<&ElementRc>,
|
||||
) -> Option<ElementRc> {
|
||||
let click_position = LogicalPoint::from_lengths(LogicalLength::new(x), LogicalLength::new(y));
|
||||
|
||||
let Some(c) = selected_element else {
|
||||
unselect_element();
|
||||
return None;
|
||||
selected_element: &ElementRc,
|
||||
layer: usize,
|
||||
) {
|
||||
eprintln!(" select_element({}, {layer})", selected_element.borrow().id);
|
||||
let Some(position) = component_instance.element_position(&selected_element) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(position) = component_instance.element_position(&c) else {
|
||||
return None;
|
||||
let secondary_positions = if let Some((path, offset)) = element_offset(selected_element) {
|
||||
component_instance.component_positions(path, offset)
|
||||
} else {
|
||||
ComponentPositions::default()
|
||||
};
|
||||
if position.contains(click_position) {
|
||||
let secondary_positions = if let Some((path, offset)) = element_offset(&c) {
|
||||
component_instance.component_positions(path, offset)
|
||||
} else {
|
||||
ComponentPositions::default()
|
||||
};
|
||||
|
||||
super::set_selected_element(Some((&c, position)), secondary_positions);
|
||||
let document_position = lsp_element_position(&c);
|
||||
if !document_position.0.is_empty() {
|
||||
super::ask_editor_to_show_document(document_position.0, document_position.1);
|
||||
}
|
||||
return Some(c.clone());
|
||||
super::set_selected_element(Some((&selected_element, position, layer)), secondary_positions);
|
||||
let document_position = lsp_element_position(&selected_element);
|
||||
if !document_position.0.is_empty() {
|
||||
super::ask_editor_to_show_document(document_position.0, document_position.1);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// triggered from the UI, running in UI thread
|
||||
pub fn select_element_at_impl(
|
||||
x: f32,
|
||||
y: f32,
|
||||
component_instance: &ComponentInstance,
|
||||
root_element: &ElementRc,
|
||||
current_element: Option<&ElementRc>,
|
||||
reverse: bool,
|
||||
) -> Option<ElementRc> {
|
||||
let re = root_element.borrow();
|
||||
let mut fw_iter = re.children.iter();
|
||||
let mut bw_iter = re.children.iter().rev();
|
||||
|
||||
let iterator: &mut dyn Iterator<Item = &ElementRc> =
|
||||
if reverse { &mut bw_iter } else { &mut fw_iter };
|
||||
|
||||
let mut skip = current_element.is_some();
|
||||
for c in &mut *iterator {
|
||||
let c = self_or_embedded_component_root(c);
|
||||
|
||||
if skip {
|
||||
if let Some(ce) = current_element {
|
||||
if Rc::ptr_eq(ce, &c) {
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(result) = select_element(x, y, component_instance, Some(&c)) {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn element_offset(element: &ElementRc) -> Option<(PathBuf, u32)> {
|
||||
|
|
@ -176,23 +128,84 @@ fn root_element(component_instance: &ComponentInstance) -> ElementRc {
|
|||
}
|
||||
}
|
||||
|
||||
fn parent_element(root_element: &ElementRc, element: &ElementRc) -> Option<ElementRc> {
|
||||
for c in &root_element.borrow().children {
|
||||
if Rc::ptr_eq(c, element) {
|
||||
return Some(root_element.clone());
|
||||
fn visit_tree_element(
|
||||
x: f32,
|
||||
y: f32,
|
||||
component_instance: &ComponentInstance,
|
||||
root_element: &ElementRc,
|
||||
current_element: &ElementRc,
|
||||
target_layer: usize,
|
||||
current_layer: usize,
|
||||
switch_files: bool,
|
||||
previous: &(usize, Option<ElementRc>),
|
||||
) -> ((usize, Option<ElementRc>), (usize, Option<ElementRc>)) {
|
||||
let ce = self_or_embedded_component_root(current_element);
|
||||
|
||||
let mut current_layer = current_layer;
|
||||
let mut previous = previous.clone();
|
||||
|
||||
for c in ce.borrow().children.iter().rev() {
|
||||
let (p, (ncl, fe)) = visit_tree_element(
|
||||
x,
|
||||
y,
|
||||
component_instance,
|
||||
root_element,
|
||||
c,
|
||||
target_layer,
|
||||
current_layer,
|
||||
switch_files,
|
||||
&previous,
|
||||
);
|
||||
|
||||
current_layer = ncl;
|
||||
previous = p;
|
||||
|
||||
if fe.is_some() {
|
||||
return (previous, (current_layer, fe));
|
||||
}
|
||||
}
|
||||
|
||||
for c in &root_element.borrow().children {
|
||||
if let Some(p) = parent_element(c, element) {
|
||||
return Some(p);
|
||||
if element_covers_point(x, y, component_instance, &ce)
|
||||
&& !Rc::ptr_eq(current_element, root_element)
|
||||
{
|
||||
current_layer += 1;
|
||||
|
||||
let same_source = (|| {
|
||||
let Some(re) = &root_element.borrow().node else {
|
||||
return false;
|
||||
};
|
||||
let Some(ce) = &ce.borrow().node else {
|
||||
return false;
|
||||
};
|
||||
Rc::ptr_eq(&re.source_file, &ce.source_file)
|
||||
})();
|
||||
let file_ok = switch_files || same_source;
|
||||
|
||||
if file_ok && current_layer < target_layer && current_layer > previous.0 {
|
||||
eprintln!(
|
||||
" visit: {x},{y} (target: {target_layer}/{current_layer}): {} => Found prev candidate in self!",
|
||||
ce.borrow().id
|
||||
);
|
||||
previous = (current_layer, Some(ce.clone()))
|
||||
}
|
||||
|
||||
if file_ok && current_layer > target_layer {
|
||||
eprintln!(
|
||||
" visit: {x},{y} (target: {target_layer}/{current_layer}): {} => Found next in self!",
|
||||
ce.borrow().id
|
||||
);
|
||||
return (previous, (current_layer, Some(ce)));
|
||||
}
|
||||
|
||||
if file_ok && current_layer < target_layer {
|
||||
previous = (current_layer, Some(ce.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
// eprintln!(" visit: {x},{y} (target: {target_layer}/{current_layer}): {} => mot found", current_element.borrow().id);
|
||||
(previous, (current_layer, None))
|
||||
}
|
||||
|
||||
// triggered from the UI, running in UI thread
|
||||
pub fn select_element_at(x: f32, y: f32) {
|
||||
let Some(component_instance) = super::component_instance() else {
|
||||
return;
|
||||
|
|
@ -200,75 +213,54 @@ pub fn select_element_at(x: f32, y: f32) {
|
|||
|
||||
let root_element = root_element(&component_instance);
|
||||
|
||||
if let Some(selected_element) = super::selected_element() {
|
||||
if let Some((selected_element, _)) = super::selected_element() {
|
||||
if element_covers_point(x, y, &component_instance, &selected_element) {
|
||||
// We clicked on the already selected element: Do nothing!
|
||||
return;
|
||||
}
|
||||
|
||||
let mut parent = parent_element(&root_element, &selected_element);
|
||||
while let Some(p) = &parent {
|
||||
if select_element_at_impl(x, y, &component_instance, p, None, true).is_some() {
|
||||
return;
|
||||
}
|
||||
parent = parent_element(&root_element, p);
|
||||
}
|
||||
}
|
||||
|
||||
select_element_at_impl(x, y, &component_instance, &root_element, None, true);
|
||||
let (_, (layer, next)) = visit_tree_element(
|
||||
x,
|
||||
y,
|
||||
&component_instance,
|
||||
&root_element,
|
||||
&root_element,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
&(0, None),
|
||||
);
|
||||
if let Some(n) = next {
|
||||
select_element(&component_instance, &n, layer);
|
||||
}
|
||||
}
|
||||
|
||||
// triggered from the UI, running in UI thread
|
||||
pub fn select_element_down(x: f32, y: f32, reverse: bool) {
|
||||
pub fn select_element_behind(x: f32, y: f32, switch_files: bool, reverse: bool) {
|
||||
let Some(component_instance) = super::component_instance() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// We have an actively selected element (via the earlier click-event :-):
|
||||
let Some(selected_element) = super::selected_element() else {
|
||||
return;
|
||||
};
|
||||
let root_element = root_element(&component_instance);
|
||||
let target_layer = super::selected_element().map(|(_, l)| l).unwrap_or_default();
|
||||
eprintln!("select_element_behind: {x},{y} (switch: {switch_files}, reverse: {reverse}), target: {target_layer}");
|
||||
|
||||
if !reverse {
|
||||
let _ = select_element_at_impl(x, y, &component_instance, &selected_element, None, true);
|
||||
} else {
|
||||
if element_covers_point(x, y, &component_instance, &selected_element) {
|
||||
let _ = select_element(
|
||||
x,
|
||||
y,
|
||||
&component_instance,
|
||||
parent_element(&root_element(&component_instance), &selected_element).as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// triggered from the UI, running in UI thread
|
||||
pub fn select_element_front_to_back(x: f32, y: f32, reverse: bool) {
|
||||
let Some(component_instance) = super::component_instance() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// We have an actively selected element (via the earlier click-event :-):
|
||||
let Some(selected_element) = super::selected_element() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if element_covers_point(x, y, &component_instance, &selected_element) {
|
||||
let Some(parent_element) =
|
||||
parent_element(&root_element(&component_instance), &selected_element)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// We clicked on the already selected element: Do nothing!
|
||||
let _ = select_element_at_impl(
|
||||
x,
|
||||
y,
|
||||
&component_instance,
|
||||
&parent_element,
|
||||
Some(&selected_element),
|
||||
!reverse,
|
||||
);
|
||||
return;
|
||||
let (previous, next) = visit_tree_element(
|
||||
x,
|
||||
y,
|
||||
&component_instance,
|
||||
&root_element,
|
||||
&root_element,
|
||||
target_layer,
|
||||
0,
|
||||
switch_files,
|
||||
&(0, None),
|
||||
);
|
||||
eprintln!("select_element_behind: {x},{y} (switch: {switch_files}, reverse: {reverse}) => Prev: {:?}, Next: {:?}", previous.1.as_ref().map(|e| e.borrow().id.clone()), next.1.as_ref().map(|e| e.borrow().id.clone()));
|
||||
let to_select = if reverse { previous } else { next };
|
||||
eprintln!("select_element_behind: {x},{y} (switch: {switch_files}, reverse: {reverse}) => To select: {:?}", to_select.1.as_ref().map(|e| e.borrow().id.clone()));
|
||||
if let (layer, Some(ts)) = to_select {
|
||||
eprintln!("select_element_behind: {x},{y} (switch: {switch_files}, reverse: {reverse}) => SETTING {}@{layer}", ts.borrow().id);
|
||||
select_element(&component_instance, &ts, layer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,33 +211,36 @@ struct PreviewState {
|
|||
ui: Option<super::ui::PreviewUi>,
|
||||
handle: Rc<RefCell<Option<ComponentInstance>>>,
|
||||
selected_element: Option<ElementWeak>,
|
||||
selected_layer: usize,
|
||||
}
|
||||
|
||||
thread_local! {static PREVIEW_STATE: std::cell::RefCell<PreviewState> = Default::default();}
|
||||
|
||||
pub fn set_selected_element(
|
||||
element_position: Option<(&ElementRc, LogicalRect)>,
|
||||
element_position: Option<(&ElementRc, LogicalRect, usize)>,
|
||||
positions: slint_interpreter::highlight::ComponentPositions,
|
||||
) {
|
||||
PREVIEW_STATE.with(move |preview_state| {
|
||||
let mut preview_state = preview_state.borrow_mut();
|
||||
if let Some((e, _)) = element_position.as_ref() {
|
||||
if let Some((e, _, l)) = element_position.as_ref() {
|
||||
preview_state.selected_element = Some(Rc::downgrade(e));
|
||||
preview_state.selected_layer = *l;
|
||||
} else {
|
||||
preview_state.selected_element = None;
|
||||
preview_state.selected_layer = 0;
|
||||
}
|
||||
|
||||
super::set_selections(preview_state.ui.as_ref(), element_position, positions);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn selected_element() -> Option<ElementRc> {
|
||||
pub fn selected_element() -> Option<(ElementRc, usize)> {
|
||||
PREVIEW_STATE.with(move |preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
let Some(weak) = &preview_state.selected_element else {
|
||||
return None;
|
||||
};
|
||||
Weak::upgrade(&weak)
|
||||
Weak::upgrade(&weak).map(|e| (e, preview_state.selected_layer))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@ pub fn create_ui(style: String) -> Result<PreviewUi, PlatformError> {
|
|||
});
|
||||
ui.on_unselect(super::element_selection::unselect_element);
|
||||
ui.on_select_at(super::element_selection::select_element_at);
|
||||
ui.on_select_front_to_back(super::element_selection::select_element_front_to_back);
|
||||
ui.on_select_down(super::element_selection::select_element_down);
|
||||
ui.on_select_behind(super::element_selection::select_element_behind);
|
||||
ui.on_can_drop(super::can_drop_component);
|
||||
ui.on_drop(super::drop_component);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,16 +48,17 @@ struct PreviewState {
|
|||
lsp_notifier: Option<SignalLspFunction>,
|
||||
resource_url_mapper: Option<ResourceUrlMapperFunction>,
|
||||
selected_element: Option<ElementWeak>,
|
||||
selected_layer: usize,
|
||||
}
|
||||
thread_local! {static PREVIEW_STATE: std::cell::RefCell<PreviewState> = Default::default();}
|
||||
|
||||
pub fn selected_element() -> Option<ElementRc> {
|
||||
pub fn selected_element() -> Option<(ElementRc, usize)> {
|
||||
PREVIEW_STATE.with(move |preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
let Some(weak) = &preview_state.selected_element else {
|
||||
return None;
|
||||
};
|
||||
Weak::upgrade(&weak)
|
||||
Weak::upgrade(&weak).map(|e| (e, preview_state.selected_layer))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -68,15 +69,17 @@ pub fn component_instance() -> Option<ComponentInstance> {
|
|||
}
|
||||
|
||||
pub fn set_selected_element(
|
||||
element_position: Option<(&ElementRc, LogicalRect)>,
|
||||
element_position: Option<(&ElementRc, LogicalRect, usize)>,
|
||||
positions: ComponentPositions,
|
||||
) {
|
||||
PREVIEW_STATE.with(move |preview_state| {
|
||||
let mut preview_state = preview_state.borrow_mut();
|
||||
if let Some((e, _)) = element_position.as_ref() {
|
||||
if let Some((e, _, l)) = element_position.as_ref() {
|
||||
preview_state.selected_element = Some(Rc::downgrade(e));
|
||||
preview_state.selected_layer = *l;
|
||||
} else {
|
||||
preview_state.selected_element = None;
|
||||
preview_state.selected_layer = 0;
|
||||
}
|
||||
|
||||
super::set_selections(preview_state.ui.as_ref(), element_position, positions);
|
||||
|
|
|
|||
|
|
@ -36,8 +36,7 @@ export component DrawArea {
|
|||
out property <length> preview-area-height: preview-visible ? i-preview-area-container.height : 0px;
|
||||
|
||||
callback select-at(/* x */ length, /* y */ length);
|
||||
callback select-front-to-back(/* x */ length, /* y */ length, /* reverse? */ bool);
|
||||
callback select-down(/* x */ length, /* y */ length, /* reverse? */ bool);
|
||||
callback select-behind(/* x */ length, /* y */ length, /* stay_in_file? */ bool, /* reverse */ bool);
|
||||
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
||||
callback unselect();
|
||||
|
||||
|
|
@ -121,13 +120,9 @@ export component DrawArea {
|
|||
// This needs to fire AFTER clicked and double-clicked to work :-/
|
||||
if (event.kind == PointerEventKind.up) {
|
||||
if (self.selection-kind == SelectionKind.select_up_or_down) {
|
||||
root.select_down(self.selection-x, self.selection-y, event.modifiers.shift);
|
||||
root.select_behind(self.selection-x, self.selection-y, event.modifiers.control, event.modifiers.shift);
|
||||
} else if (self.selection-kind == SelectionKind.select-at) {
|
||||
if (event.modifiers.control) {
|
||||
root.select-front-to-back(self.selection-x, self.selection-y, event.modifiers.shift);
|
||||
} else {
|
||||
root.select-at(self.selection-x, self.selection-y);
|
||||
}
|
||||
root.select-at(self.selection-x, self.selection-y);
|
||||
}
|
||||
}
|
||||
self.selection-kind = SelectionKind.none;
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ export component PreviewUi inherits Window {
|
|||
callback can-drop(/* component_type */ string, /* x */ length, /* y */ length) -> bool;
|
||||
callback drop(/* component_type */ string, /* x */ length, /* y */ length);
|
||||
callback select-at(/* x */ length, /* y */ length);
|
||||
callback select-down(/* x */ length, /* y */ length, /* upwards? */ bool);
|
||||
callback select-front-to-back(/* x */ length, /* y */ length, /* backwards? */ bool);
|
||||
callback select-behind(/* x */ length, /* y */ length, /* stay_in_file* */ bool, /* reverse */ bool);
|
||||
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
||||
callback style-changed();
|
||||
callback unselect();
|
||||
|
|
@ -110,8 +109,7 @@ export component PreviewUi inherits Window {
|
|||
selections <=> root.selections;
|
||||
|
||||
select-at(x, y) => { root.select-at(x, y); }
|
||||
select-front-to-back(x, y, front) => { root.select-front-to-back(x, y, front); }
|
||||
select-down(x, y, up) => { root.select-down(x, y, up); }
|
||||
select-behind(x, y, stay_in_file, reverse) => { root.select-behind(x, y, stay_in_file, reverse); }
|
||||
show-document(url, line, column) => { root.show-document(url, line, column); }
|
||||
unselect() => { root.unselect(); }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue