Skip to content

Commit

Permalink
Slintpad: Add picker mode where you can click on something in the pre…
Browse files Browse the repository at this point in the history
…view and the editor focuses on that (slint-ui#2567)

* compiler: Make mapping from source offset to line/column more reusable
* compiler: Improve mapping of offset to line/column
* Fix unit tests after line mapping update
* interpreter: Add code to have a element picker mode
* slintpad: Add picker mode to the preview
* slintpad: Do not try to highlight "empty" highlight requests
* Slintpad: Cycle through all the possible elements in design mode
* Slintpad: Ignore builtins and eat less clicks
* Slintpad: Highlight the element selected in design mode
* Slintpad: Do not use static mut variable in design mode
* slintpad: Rename `set_current_element_information_callback`
* Interpreter: Do not use unsafe in design mode code

Done with: @ogoffart and @tronical
  • Loading branch information
hunger authored Apr 18, 2023
1 parent bd63218 commit 893983e
Show file tree
Hide file tree
Showing 12 changed files with 497 additions and 43 deletions.
35 changes: 34 additions & 1 deletion api/wasm-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ impl CompilationResult {
}

#[wasm_bindgen(typescript_custom_section)]
const IMPORT_CALLBACK_FUNCTION_SECTION: &'static str = r#"
const CALLBACK_FUNCTION_SECTION: &'static str = r#"
type ImportCallbackFunction = (url: string) => Promise<string>;
type CurrentElementInformationCallbackFunction = (url: string, start_line: number, start_column: number, end_line: number, end_column: number) => void;
"#;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "ImportCallbackFunction")]
pub type ImportCallbackFunction;

#[wasm_bindgen(typescript_type = "CurrentElementInformationCallbackFunction")]
pub type CurrentElementInformationCallbackFunction;
}

/// Compile the content of a string.
Expand Down Expand Up @@ -203,6 +207,35 @@ impl WrappedInstance {
self.0.highlight(_path.into(), _offset);
let _ = slint_interpreter::invoke_from_event_loop(|| {}); // wake event loop
}

/// THIS FUNCTION IS NOT PART THE PUBLIC API!
/// Request information on what to highlight in the editor based on clicks in the UI
#[cfg(feature = "highlight")]
#[wasm_bindgen]
pub fn set_design_mode(&self, active: bool) {
self.0.set_design_mode(active);
let _ = slint_interpreter::invoke_from_event_loop(|| {}); // wake event loop
}

/// THIS FUNCTION IS NOT PART THE PUBLIC API!
/// Request information on what to highlight in the editor based on clicks in the UI
#[cfg(feature = "highlight")]
#[wasm_bindgen]
pub fn on_element_selected(&self, callback: CurrentElementInformationCallbackFunction) {
self.0.on_element_selected(Box::new(
move |url: &str, start_line: u32, start_column: u32, end_line: u32, end_column: u32| {
let args = js_sys::Array::of5(
&url.into(),
&start_line.into(),
&start_column.into(),
&end_line.into(),
&end_column.into(),
);
let callback = js_sys::Function::from(callback.clone());
let _ = callback.apply(&JsValue::UNDEFINED, &args);
},
));
}
}

/// Register DOM event handlers on all instance and set up the event loop for that.
Expand Down
38 changes: 23 additions & 15 deletions internal/compiler/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,31 @@ impl SourceFileInner {
Rc::new(Self { path, ..Default::default() })
}

/// Returns a tuple with the line (starting at 1) and column number (starting at 0)
pub fn line_column(&self, offset: usize) -> (usize, usize) {
let line_offsets = self.line_offsets();
line_offsets.binary_search(&offset).map_or_else(
|line| {
if line == 0 {
(1, offset)
} else {
(line + 1, line_offsets.get(line - 1).map_or(0, |x| offset - x))
}
},
|line| (line + 1, 0),
)
}

fn line_offsets(&self) -> &[usize] {
self.line_offsets.get_or_init(|| {
self.source
.as_ref()
.map(|s| {
s.bytes()
.enumerate()
.filter_map(|(i, c)| if c == b'\n' { Some(i) } else { None })
// Add the offset one past the '\n' into the index: That's the first char
// of the new line!
.filter_map(|(i, c)| if c == b'\n' { Some(i + 1) } else { None })
.collect()
})
.unwrap_or_default()
Expand Down Expand Up @@ -205,20 +222,11 @@ impl Diagnostic {
/// Returns a tuple with the line (starting at 1) and column number (starting at 0)
pub fn line_column(&self) -> (usize, usize) {
let offset = self.span.span.offset;
let line_offsets = match &self.span.source_file {
None => return (0, 0),
Some(sl) => sl.line_offsets(),
};
line_offsets.binary_search(&offset).map_or_else(
|line| {
if line == 0 {
(line + 1, offset)
} else {
(line + 1, line_offsets.get(line - 1).map_or(0, |x| offset - x))
}
},
|line| (line + 1, 0),
)

match &self.span.source_file {
None => (0, 0),
Some(sl) => sl.line_column(offset),
}
}

/// return the path of the source file where this error is attached
Expand Down
4 changes: 2 additions & 2 deletions internal/compiler/tests/syntax_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//!
//! Meaning that there must an error following with an error message for that regular expression in the position
//! on the line above at the column pointed by the caret.
//! If there are two carets: ` ^^error{some_regexpr}` then it means two line above, and so on with more carets.
//! If there are two carets: ` ^^error{some_regexp}` then it means two line above, and so on with more carets.
//! `^warning{regexp}` is also supported.
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -97,7 +97,7 @@ fn process_diagnostics(
let lines = source
.bytes()
.enumerate()
.filter_map(|(i, c)| if c == b'\n' { Some(i) } else { None })
.filter_map(|(i, c)| if c == b'\n' { Some(i + 1) } else { None })
.collect::<Vec<usize>>();

// Find expected errors in the file. The first caret (^) points to the expected column. The number of
Expand Down
16 changes: 16 additions & 0 deletions internal/interpreter/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,22 @@ impl ComponentInstance {
pub fn highlight(&self, path: PathBuf, offset: u32) {
crate::highlight::highlight(&self.inner, path, offset);
}

/// Request information on clicked object
///
/// WARNING: this is not part of the public API
#[cfg(feature = "highlight")]
pub fn set_design_mode(&self, active: bool) {
crate::highlight::set_design_mode(&self.inner, active);
}

/// Register callback to handle current item information
///
/// WARNING: this is not part of the public API
#[cfg(feature = "highlight")]
pub fn on_element_selected(&self, callback: Box<dyn Fn(&str, u32, u32, u32, u32) -> ()>) {
crate::highlight::on_element_selected(&self.inner, callback);
}
}

impl ComponentHandle for ComponentInstance {
Expand Down
2 changes: 1 addition & 1 deletion internal/interpreter/dynamic_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ pub async fn load(
}

#[cfg(feature = "highlight")]
crate::highlight::add_highlight_items(&doc);
crate::highlight::add_highlighting(&doc);

(Ok(generate_component(&doc.root_component, guard)), diag)
}
Expand Down
Loading

0 comments on commit 893983e

Please sign in to comment.