Skip to content

Commit

Permalink
live-preview: More filter options in Selection Popup (#7070)
Browse files Browse the repository at this point in the history
* live-preview: More filter options in Selection Popup

* Hide layouts,
* Hide interactive elements
* Hide others

@szecket did a lot of UI polish that I just merged here
so that I do not need to show what I made!

* live-preview: Remove TouchArea suffix
  • Loading branch information
hunger authored Dec 12, 2024
1 parent e0ad561 commit da7ba2f
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 33 deletions.
41 changes: 31 additions & 10 deletions tools/lsp/preview/element_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,26 +446,47 @@ pub fn selection_stack_at(

pub fn filter_sort_selection_stack(
model: slint::ModelRc<crate::preview::ui::SelectionStackFrame>,
filter: slint::SharedString,
filter_text: slint::SharedString,
filter: crate::preview::ui::SelectionStackFilter,
) -> slint::ModelRc<crate::preview::ui::SelectionStackFrame> {
use crate::preview::ui::{SelectionStackFilter, SelectionStackFrame};
use slint::ModelExt;

let filter = filter.to_string();
fn filter_fn(frame: &SelectionStackFrame, filter: SelectionStackFilter) -> bool {
match filter {
SelectionStackFilter::Nothing => false,
SelectionStackFilter::Layouts => frame.is_layout,
SelectionStackFilter::Interactive => frame.is_interactive,
SelectionStackFilter::Others => !frame.is_interactive && !frame.is_layout,
SelectionStackFilter::LayoutsAndInteractive => frame.is_layout || frame.is_interactive,
SelectionStackFilter::LayoutsAndOthers => {
frame.is_layout || (!frame.is_layout && !frame.is_interactive)
}
SelectionStackFilter::InteractiveAndOthers => {
frame.is_interactive || (!frame.is_layout && !frame.is_interactive)
}
SelectionStackFilter::Everything => true,
}
}

let filter_text = filter_text.to_string();

if filter.is_empty() {
if filter_text.is_empty() && filter == SelectionStackFilter::Everything {
model
} else if filter.as_str().chars().any(|c| !c.is_lowercase()) {
} else if filter_text.as_str().chars().any(|c| !c.is_lowercase()) {
Rc::new(model.filter(move |frame| {
frame.id.contains(&filter)
|| frame.type_name.contains(&filter)
|| frame.file_name.contains(&filter)
filter_fn(frame, filter)
&& (frame.id.contains(&filter_text)
|| frame.type_name.contains(&filter_text)
|| frame.file_name.contains(&filter_text))
}))
.into()
} else {
Rc::new(model.filter(move |frame| {
frame.id.to_lowercase().contains(&filter)
|| frame.type_name.to_lowercase().contains(&filter)
|| frame.file_name.to_lowercase().contains(&filter)
filter_fn(frame, filter)
&& (frame.id.to_lowercase().contains(&filter_text)
|| frame.type_name.to_lowercase().contains(&filter_text)
|| frame.file_name.to_lowercase().contains(&filter_text))
}))
.into()
}
Expand Down
13 changes: 12 additions & 1 deletion tools/lsp/ui/api.slint
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ export struct SelectionStackFrame {
id: string,
}

export enum SelectionStackFilter {
Nothing,
Layouts,
Interactive,
Others,
LayoutsAndInteractive,
LayoutsAndOthers,
InteractiveAndOthers,
Everything,
}

/// A Range in a source
export struct Range {
start: int,
Expand Down Expand Up @@ -404,7 +415,7 @@ export global Api {

// ## Element selection:
callback selection-stack-at(x: length, y: length) -> [SelectionStackFrame];
pure callback filter-sort-selection-stack(model: [SelectionStackFrame], filter: string) -> [SelectionStackFrame];
pure callback filter-sort-selection-stack(model: [SelectionStackFrame], filter_text: string, filter: SelectionStackFilter) -> [SelectionStackFrame];
pure callback find-selected-selection-stack-frame([SelectionStackFrame]) -> SelectionStackFrame;
callback select-element(file: string, offset: int, x: length, y: length);

Expand Down
135 changes: 113 additions & 22 deletions tools/lsp/ui/components/selection-popup.slint
Original file line number Diff line number Diff line change
@@ -1,11 +1,69 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

import { ListView, Palette, ScrollView, LineEdit } from "std-widgets.slint";
import { Button, CheckBox, ListView, Palette, ScrollView, LineEdit } from "std-widgets.slint";
import { EditorFontSettings, EditorSizeSettings, EditorSpaceSettings, EditorPalette } from "./styling.slint";
import { Api, SelectionStackFrame } from "../api.slint";
import { Api, SelectionStackFrame, SelectionStackFilter } from "../api.slint";
import { Icons } from "styling.slint";

component FilterList inherits Rectangle {
out property <SelectionStackFilter> filter: {
if (!lcb.checked && !icb.checked && ocb.checked) {
return SelectionStackFilter.Others;
} else if (!lcb.checked && icb.checked && !ocb.checked) {
return SelectionStackFilter.Interactive;
} else if (!lcb.checked && icb.checked && ocb.checked) {
return SelectionStackFilter.InteractiveAndOthers;
} else if (lcb.checked && !icb.checked && !ocb.checked) {
return SelectionStackFilter.Layouts;
} else if (lcb.checked && !icb.checked && ocb.checked) {
return SelectionStackFilter.LayoutsAndOthers;
} else if (lcb.checked && icb.checked && !ocb.checked) {
return SelectionStackFilter.LayoutsAndInteractive;
} else if (lcb.checked && icb.checked && ocb.checked) {
return SelectionStackFilter.Everything;
}

return SelectionStackFilter.Nothing;
}

out property <bool> has-filter: !lcb.checked || !icb.checked || !ocb.checked;

border-color: Palette.border;
border-width: 1px;
border-radius: EditorSizeSettings.radius;

drop-shadow-blur: EditorSpaceSettings.default-padding;
drop-shadow-color: Palette.foreground.transparentize(0.9);

background: Palette.alternate-background;

TouchArea {
// Just block events from reaching other TouchAreas!
}

VerticalLayout {
padding: EditorSpaceSettings.default-padding;
spacing: EditorSpaceSettings.default-spacing;

lcb := CheckBox {
text: @tr("Layouts");
checked: true;
}

icb := CheckBox {
text: @tr("Interactive");
checked: true;
}

ocb := CheckBox {
text: @tr("Other");
checked: true;
}
}
}


component PopupInner inherits Rectangle {
in property <length> preview-width: 500px;
in property <length> preview-height: 900px;
Expand Down Expand Up @@ -132,6 +190,7 @@ component PopupInner inherits Rectangle {
}
}

private property <[SelectionStackFrame]> filtered-model: Api.filter-sort-selection-stack(root.selection-stack, filter-edit.text, filter-list.filter);
border-radius: EditorSizeSettings.radius;

border-width: 0.5px;
Expand All @@ -141,44 +200,67 @@ component PopupInner inherits Rectangle {
border-color: lightgray;

width: 250px;
init => {
filter-edit.focus();

TouchArea {
changed has-hover => {
if self.has-hover {
root.unselect-previewed-selection();
filter-list.visible = false;
}
}
}

VerticalLayout {
padding-top: EditorSpaceSettings.default-padding;
padding-bottom: EditorSpaceSettings.default-padding;
padding: EditorSpaceSettings.default-padding;
spacing: EditorSpaceSettings.default-spacing;

header := HorizontalLayout {
padding-right: EditorSpaceSettings.default-padding;
spacing: EditorSpaceSettings.default-spacing;

Rectangle {
Image {
source: Icons.filter;
colorize: Palette.alternate-foreground;
width: EditorSizeSettings.default-icon-width * 0.8;
height: EditorSizeSettings.default-icon-width * 0.8;
y: filter-edit.height / 2 - self.height / 2;
filter-button := Button {
private property <bool> filter-state: filter-list.filter != SelectionStackFilter.Everything || filter-edit.text != "";

changed filter-state => {
self.checked = filter-state;
}

icon: Icons.filter;
checkable: true;
colorize-icon: true;

clicked => {
filter-list.visible = !filter-list.visible;
filter-edit.clear-focus();
self.checked = filter-state;
}
}

filter-edit := LineEdit {
placeholder-text: "Filter";
min-width: 50px;

init => {
self.focus();
changed has-focus => {
if self.has-focus {
filter-list.visible = false;
}
}
}
}

ScrollView {
if filtered-model.length == 0: Rectangle {
width: 100%;
height: (root.visible-frames * root.frame-height);

list-view := VerticalLayout {
Text {
text: @tr("No match");
}
}

for frame[index] in Api.filter-sort-selection-stack(root.selection-stack, filter-edit.text): frame-rect := Rectangle {
if filtered-model.length >= 1: ScrollView {
height: (root.visible-frames * root.frame-height);

list-view := VerticalLayout {
for frame[index] in root.filtered-model: frame-rect := Rectangle {
height: root.frame-height;

function frame-color(frame: SelectionStackFrame) -> brush {
Expand Down Expand Up @@ -317,7 +399,7 @@ component PopupInner inherits Rectangle {
}

Text {
text: frame.type-name + (frame.is-interactive ? " (TouchArea)" : "");
text: frame.type-name;
overflow: elide;
color: frame.is-selected ? Palette.accent-foreground : Palette.foreground;
font-size: EditorFontSettings.label-sub.font-size;
Expand Down Expand Up @@ -354,16 +436,25 @@ component PopupInner inherits Rectangle {
}
changed has-hover => {
if self.has-hover {
filter-list.visible = false;
Api.select-element(frame.element-path, frame.element-offset, frame.x * root.preview-width, frame.y * root.preview-height);
} else {
root.unselect-previewed-selection();
}
}
}
}
}
}
}

filter-list := FilterList {
x: filter-button.x + EditorSpaceSettings.default-spacing;
y: filter-button.y + filter-button.height + EditorSpaceSettings.default-spacing;

width: self.preferred-width;
height: self.preferred-height;

visible: false;
}
}

export component SelectionPopup {
Expand Down

0 comments on commit da7ba2f

Please sign in to comment.