diff --git a/editors/vscode/package.json b/editors/vscode/package.json index b7cdeb860..7a0850b99 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -94,6 +94,10 @@ }, "description": "The command line arguments passed to the Slint LSP server" }, + "slint.preview.show_ui": { + "type": "boolean", + "description": "Show the toolbar of the preview" + }, "slint.preview.style": { "type": "string", "description": "The default style to be used for the preview (eg: 'fluent', 'material', or 'native')" diff --git a/tools/lsp/common.rs b/tools/lsp/common.rs index a00934041..1b1b7008d 100644 --- a/tools/lsp/common.rs +++ b/tools/lsp/common.rs @@ -19,6 +19,7 @@ pub trait PreviewApi { fn load_preview(&self, component: PreviewComponent); fn config_changed( &self, + show_preview_ui: bool, style: &str, include_paths: &[PathBuf], library_paths: &HashMap, @@ -27,6 +28,12 @@ pub trait PreviewApi { /// What is the current component to preview? fn current_component(&self) -> Option; + + /// Return the currently configured `show_preview_ui` value. + /// + /// This is not ideal, but we can not access the configuration without + /// a round trip into the editor. + fn show_preview_ui(&self) -> bool; } /// The Component to preview @@ -57,6 +64,7 @@ pub enum LspToPreviewMessage { contents: String, }, SetConfiguration { + show_preview_ui: bool, style: String, include_paths: Vec, library_paths: Vec<(String, String)>, diff --git a/tools/lsp/language.rs b/tools/lsp/language.rs index 448a0e82a..2a9fdfc57 100644 --- a/tools/lsp/language.rs +++ b/tools/lsp/language.rs @@ -90,6 +90,7 @@ pub fn request_state(ctx: &std::rc::Rc) { } let style = documents.compiler_config.style.clone().unwrap_or_default(); ctx.preview.config_changed( + ctx.preview.show_preview_ui(), &style, &documents.compiler_config.include_paths, &documents.compiler_config.library_paths, @@ -1221,6 +1222,7 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> { .await?; let document_cache = &mut ctx.document_cache.borrow_mut(); + let mut show_preview_ui = false; for v in r { if let Some(o) = v.as_object() { if let Some(ip) = o.get("includePaths").and_then(|v| v.as_array()) { @@ -1244,6 +1246,10 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> { document_cache.documents.compiler_config.style = Some(style.into()); } } + show_preview_ui = o + .get("preview") + .and_then(|v| v.as_object()?.get("show_ui")?.as_bool()) + .unwrap_or_default(); } } @@ -1254,6 +1260,7 @@ pub async fn load_configuration(ctx: &Context) -> Result<()> { let cc = &document_cache.documents.compiler_config; let empty_string = String::new(); ctx.preview.config_changed( + show_preview_ui, cc.style.as_ref().unwrap_or(&empty_string), &cc.include_paths, &cc.library_paths, diff --git a/tools/lsp/main.rs b/tools/lsp/main.rs index 44e33a609..0f6f3888f 100644 --- a/tools/lsp/main.rs +++ b/tools/lsp/main.rs @@ -37,6 +37,7 @@ struct Previewer { #[allow(unused)] server_notifier: ServerNotifier, use_external_previewer: RefCell, + show_preview_ui: RefCell, to_show: RefCell>, } @@ -103,15 +104,18 @@ impl PreviewApi for Previewer { fn config_changed( &self, + _show_preview_ui: bool, _style: &str, _include_paths: &[PathBuf], _library_paths: &HashMap, ) { + *self.show_preview_ui.borrow_mut() = _show_preview_ui; if *self.use_external_previewer.borrow() { #[cfg(feature = "preview-external")] let _ = self.server_notifier.send_notification( "slint/lsp_to_preview".to_string(), crate::common::LspToPreviewMessage::SetConfiguration { + show_preview_ui: _show_preview_ui, style: _style.to_string(), include_paths: _include_paths .iter() @@ -125,7 +129,7 @@ impl PreviewApi for Previewer { ); } else { #[cfg(feature = "preview-builtin")] - preview::config_changed(_style, _include_paths, _library_paths); + preview::config_changed(_show_preview_ui, _style, _include_paths, _library_paths); } } @@ -152,6 +156,10 @@ impl PreviewApi for Previewer { fn current_component(&self) -> Option { self.to_show.borrow().clone() } + + fn show_preview_ui(&self) -> bool { + *self.show_preview_ui.borrow() + } } #[derive(Clone, clap::Parser)] @@ -176,6 +184,10 @@ struct Cli { /// Start the preview in full screen mode #[arg(long, action)] fullscreen: bool, + + /// Show the preview UI + #[arg(long, default_value = "true", action = clap::ArgAction::Set)] + show_preview_ui: bool, } enum OutgoingRequest { @@ -265,11 +277,13 @@ fn main() { std::env::set_var("SLINT_FULLSCREEN", "1"); } + let show_preview_ui = args.show_preview_ui; + #[cfg(feature = "preview-engine")] { let lsp_thread = std::thread::Builder::new() .name("LanguageServer".into()) - .spawn(|| { + .spawn(move || { /// Make sure we quit the event loop even if we panic struct QuitEventLoop; impl Drop for QuitEventLoop { @@ -279,7 +293,7 @@ fn main() { } let quit_ui_loop = QuitEventLoop; - let threads = match run_lsp_server() { + let threads = match run_lsp_server(show_preview_ui) { Ok(threads) => threads, Err(error) => { eprintln!("Error running LSP server: {}", error); @@ -304,7 +318,7 @@ fn main() { } } -pub fn run_lsp_server() -> Result { +pub fn run_lsp_server(show_preview_ui: bool) -> Result { let (connection, io_threads) = Connection::stdio(); let (id, params) = connection.initialize_start()?; @@ -313,12 +327,16 @@ pub fn run_lsp_server() -> Result { serde_json::to_value(language::server_initialize_result(&init_param.capabilities))?; connection.initialize_finish(id, initialize_result)?; - main_loop(connection, init_param)?; + main_loop(connection, init_param, show_preview_ui)?; Ok(io_threads) } -fn main_loop(connection: Connection, init_param: InitializeParams) -> Result<()> { +fn main_loop( + connection: Connection, + init_param: InitializeParams, + show_preview_ui: bool, +) -> Result<()> { let mut rh = RequestHandler::default(); register_request_handlers(&mut rh); @@ -336,6 +354,7 @@ fn main_loop(connection: Connection, init_param: InitializeParams) -> Result<()> #[cfg(all(feature = "preview-builtin", feature = "preview-external"))] use_external_previewer: RefCell::new(false), // prefer internal to_show: RefCell::new(None), + show_preview_ui: RefCell::new(show_preview_ui), }); let mut compiler_config = CompilerConfiguration::new(i_slint_compiler::generator::OutputFormat::Interpreter); diff --git a/tools/lsp/preview.rs b/tools/lsp/preview.rs index c3f8bff30..0bf4b032f 100644 --- a/tools/lsp/preview.rs +++ b/tools/lsp/preview.rs @@ -49,6 +49,8 @@ struct ContentCache { ui_is_visible: bool, design_mode: bool, default_style: String, + // Duplicate this information in case the UI is not up yet. + show_preview_ui: bool, } static CONTENT_CACHE: std::sync::OnceLock> = std::sync::OnceLock::new(); @@ -106,6 +108,7 @@ pub fn finish_parsing(ok: bool) { } pub fn config_changed( + show_preview_ui: bool, style: &str, include_paths: &[PathBuf], library_paths: &HashMap, @@ -116,15 +119,20 @@ pub fn config_changed( if cache.current.style != style || cache.current.include_paths != include_paths || cache.current.library_paths != *library_paths + || cache.show_preview_ui != show_preview_ui { cache.current.include_paths = include_paths.to_vec(); cache.current.library_paths = library_paths.clone(); cache.default_style = style; + cache.show_preview_ui = show_preview_ui; let current = cache.current.clone(); let ui_is_visible = cache.ui_is_visible; + let show_preview_ui = cache.show_preview_ui; + drop(cache); if ui_is_visible && !current.path.as_os_str().is_empty() { + set_show_preview_ui(show_preview_ui); load_preview(current); } } diff --git a/tools/lsp/preview/native.rs b/tools/lsp/preview/native.rs index 03bd16134..54d38100e 100644 --- a/tools/lsp/preview/native.rs +++ b/tools/lsp/preview/native.rs @@ -122,7 +122,7 @@ pub fn open_ui(sender: &ServerNotifier) { } } - let default_style = { + let (default_style, show_preview_ui) = { let mut cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap(); if cache.ui_is_visible { return; // UI is already up! @@ -132,21 +132,23 @@ pub fn open_ui(sender: &ServerNotifier) { let mut s = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap(); *s = Some(sender.clone()); - cache.default_style.clone() + (cache.default_style.clone(), cache.show_preview_ui) }; i_slint_core::api::invoke_from_event_loop(move || { PREVIEW_STATE.with(|preview_state| { let mut preview_state = preview_state.borrow_mut(); - open_ui_impl(&mut preview_state, default_style); + open_ui_impl(&mut preview_state, default_style, show_preview_ui); }); }) .unwrap(); } -fn open_ui_impl(preview_state: &mut PreviewState, default_style: String) { +fn open_ui_impl(preview_state: &mut PreviewState, default_style: String, show_preview_ui: bool) { // TODO: Handle Error! - let ui = preview_state.ui.get_or_insert_with(|| super::ui::create_ui(default_style).unwrap()); + let ui = preview_state + .ui + .get_or_insert_with(|| super::ui::create_ui(default_style, show_preview_ui).unwrap()); ui.on_show_document(|url, line, column| { ask_editor_to_show_document( url.as_str().to_string(), @@ -218,6 +220,17 @@ pub fn notify_diagnostics(diagnostics: &[slint_interpreter::Diagnostic]) -> Opti Some(()) } +pub fn set_show_preview_ui(show_preview_ui: bool) { + run_in_ui_thread(move || async move { + PREVIEW_STATE.with(|preview_state| { + let preview_state = preview_state.borrow(); + if let Some(ui) = &preview_state.ui { + ui.set_show_preview_ui(show_preview_ui) + } + }) + }); +} + pub fn set_current_style(style: String) { PREVIEW_STATE.with(move |preview_state| { let preview_state = preview_state.borrow_mut(); @@ -312,15 +325,15 @@ pub fn configure_design_mode(enabled: bool) { /// This runs `set_preview_factory` in the UI thread pub fn update_preview_area(compiled: slint_interpreter::ComponentDefinition, design_mode: bool) { - let default_style = { + let (default_style, show_preview_ui) = { let cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap(); - cache.default_style.clone() + (cache.default_style.clone(), cache.show_preview_ui) }; PREVIEW_STATE.with(|preview_state| { let mut preview_state = preview_state.borrow_mut(); - open_ui_impl(&mut preview_state, default_style); + open_ui_impl(&mut preview_state, default_style, show_preview_ui); let shared_handle = preview_state.handle.clone(); diff --git a/tools/lsp/preview/ui.rs b/tools/lsp/preview/ui.rs index 7c0cc8ee4..f535c8bdb 100644 --- a/tools/lsp/preview/ui.rs +++ b/tools/lsp/preview/ui.rs @@ -11,12 +11,10 @@ use slint_interpreter::{DiagnosticLevel, PlatformError}; slint::include_modules!(); -pub fn create_ui(style: String) -> Result { +pub fn create_ui(style: String, show_preview_ui: bool) -> Result { let ui = PreviewUi::new()?; - if std::env::var("SLINT_FULLSCREEN").is_ok() { - ui.set_hide_toolbar(true); - } + ui.set_show_preview_ui(show_preview_ui); // design mode: ui.on_design_mode_changed(super::set_design_mode); diff --git a/tools/lsp/preview/wasm.rs b/tools/lsp/preview/wasm.rs index 5ff7a5098..344628a86 100644 --- a/tools/lsp/preview/wasm.rs +++ b/tools/lsp/preview/wasm.rs @@ -75,7 +75,7 @@ impl PreviewConnector { reject_c.take().call1(&JsValue::UNDEFINED, &JsValue::from("PreviewConnector already set up.")).unwrap_throw(); } else { - match super::ui::create_ui(style) { + match super::ui::create_ui(style, true) { Ok(ui) => { ui.on_show_document(|url, line, column| ask_editor_to_show_document(url.as_str().to_string(), line as u32, column as u32, line as u32, column as u32)); preview_state.borrow_mut().ui = Some(ui); @@ -125,11 +125,11 @@ impl PreviewConnector { super::set_contents(&PathBuf::from(&path), contents); Ok(()) } - M::SetConfiguration { style, include_paths, library_paths } => { + M::SetConfiguration { show_preview_ui, style, include_paths, library_paths } => { let ip: Vec = include_paths.iter().map(PathBuf::from).collect(); let lp: HashMap = library_paths.iter().map(|(n, p)| (n.clone(), PathBuf::from(p))).collect(); - super::config_changed(&style, &ip, &lp); + super::config_changed(show_preview_ui, &style, &ip, &lp); Ok(()) } M::ShowPreview { path, component, style, include_paths, library_paths } => { @@ -259,6 +259,15 @@ pub fn send_message_to_lsp(message: crate::common::PreviewToLspMessage) { }) } +pub fn set_show_preview_ui(show_preview_ui: bool) { + PREVIEW_STATE.with(move |preview_state| { + let preview_state = preview_state.borrow_mut(); + if let Some(ui) = &preview_state.ui { + ui.set_show_preview_ui(show_preview_ui) + } + }); +} + pub fn set_current_style(style: String) { PREVIEW_STATE.with(move |preview_state| { let preview_state = preview_state.borrow_mut(); diff --git a/tools/lsp/ui/diagnostics-overlay.slint b/tools/lsp/ui/diagnostics-overlay.slint new file mode 100644 index 000000000..2acfd61b3 --- /dev/null +++ b/tools/lsp/ui/diagnostics-overlay.slint @@ -0,0 +1,51 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial + +// cSpell: ignore Heade + +import { ListView, VerticalBox } from "std-widgets.slint"; + +export struct Diagnostics { + level: string, + message: string, + url: string, + line: int, + column: int, +} + +export component DiagnosticsOverlay { + in property<[Diagnostics]> diagnostics; + callback show-document(/* url */ string, /* line */ int, /* column */ int); + + if (root.diagnostics.length != 0): Rectangle { + background: #fff; + + VerticalBox { + Text { + color: #000; + text: "Compilation failed:"; + } + + ListView { + width: parent.width - 10px; + height: parent.height - 10px; + + padding: 5px; + + for diag in root.diagnostics: Rectangle { + TouchArea { + mouse-cursor: pointer; + clicked => { root.show_document(diag.url, diag.line, diag.column); } + + Text { + width: 100%; + wrap: word-wrap; + color: #000; + text: diag.level + ": " + diag.message; + } + } + } + } + } + } +} diff --git a/tools/lsp/ui/main.slint b/tools/lsp/ui/main.slint index 20ccc7575..1f084654c 100644 --- a/tools/lsp/ui/main.slint +++ b/tools/lsp/ui/main.slint @@ -5,22 +5,17 @@ import { Button, ComboBox, HorizontalBox, ListView, ScrollView, VerticalBox } from "std-widgets.slint"; import { HeaderBar } from "header-bar.slint"; +import { Diagnostics, DiagnosticsOverlay } from "diagnostics-overlay.slint"; -export struct Diagnostics { - level: string, - message: string, - url: string, - line: int, - column: int, -} +export { Diagnostics } export component PreviewUi inherits Window { in property<[string]> known-styles; in property<[Diagnostics]> diagnostics; in property status-text; - in property preview-area <=> i-preview-area-container.component-factory; + in property preview-area; in-out property current-style; - in property hide-toolbar; + in property show-preview-ui; callback design-mode-changed(/* enabled */ bool); callback style-changed(); @@ -29,8 +24,24 @@ export component PreviewUi inherits Window { title: "Slint Live-Preview"; icon: @image-url("assets/slint-logo-small-light.png"); - VerticalLayout { - if !hide_toolbar: HeaderBar { + if (!show-preview-ui): VerticalLayout { + no-ui-drawing-rect := Rectangle { + /* background: checkerboard pattern; */ + ComponentContainer { + component-factory <=> root.preview-area; + width: clamp(no-ui-drawing-rect.width, self.min-width, self.max-width); + height: clamp(no-ui-drawing-rect.height, self.min-height, self.max-height); + } + + // Diagnostics overlay: + DiagnosticsOverlay { + diagnostics <=> root.diagnostics; + show-document(url, line, column) => { root.show-document(url, line, column); } + } + } + } + if (show-preview-ui): VerticalLayout { + HeaderBar { vertical-stretch: 0.0; height: self.preferred-height; @@ -64,43 +75,16 @@ export component PreviewUi inherits Window { drawing-rect := Rectangle { /* background: checkerboard pattern; */ - i-preview-area-container := ComponentContainer { - width: max(self.min-width, min(self.max-width, drawing-rect.width)); - height: max(self.min-height, min(self.max-height, drawing-rect.height)); + ComponentContainer { + component-factory <=> root.preview-area; + width: clamp(drawing-rect.width, self.min-width, self.max-width); + height: clamp(drawing-rect.height, self.min-height, self.max-height); } // Diagnostics overlay: - if (root.diagnostics.length != 0): Rectangle { - background: #fff; - - VerticalBox { - - Text { - color: #000; - text: "Compilation failed:"; - } - - ListView { - width: parent.width - 10px; - height: parent.height - 10px; - - padding: 5px; - - for diag in root.diagnostics: Rectangle { - TouchArea { - mouse-cursor: pointer; - clicked => { root.show_document(diag.url, diag.line, diag.column); } - - Text { - width: 100%; - wrap: word-wrap; - color: #000; - text: diag.level + ": " + diag.message; - } - } - } - } - } + DiagnosticsOverlay { + diagnostics <=> root.diagnostics; + show-document(url, line, column) => { root.show-document(url, line, column); } } } } diff --git a/tools/lsp/wasm_main.rs b/tools/lsp/wasm_main.rs index 53b572ab3..2b1ec7a57 100644 --- a/tools/lsp/wasm_main.rs +++ b/tools/lsp/wasm_main.rs @@ -46,6 +46,7 @@ pub mod wasm_prelude { struct Previewer { server_notifier: ServerNotifier, to_show: RefCell>, + show_preview_ui: RefCell, } impl PreviewApi for Previewer { @@ -94,14 +95,18 @@ impl PreviewApi for Previewer { fn config_changed( &self, + show_preview_ui: bool, style: &str, include_paths: &[PathBuf], library_paths: &HashMap, ) { + *self.show_preview_ui.borrow_mut() = show_preview_ui; + #[cfg(feature = "preview-external")] let _ = self.server_notifier.send_notification( "slint/lsp_to_preview".to_string(), crate::common::LspToPreviewMessage::SetConfiguration { + show_preview_ui, style: style.to_string(), include_paths: include_paths .iter() @@ -129,6 +134,10 @@ impl PreviewApi for Previewer { fn current_component(&self) -> Option { self.to_show.borrow().clone() } + + fn show_preview_ui(&self) -> bool { + *self.show_preview_ui.borrow() + } } #[derive(Clone)] @@ -269,6 +278,7 @@ pub fn create( let preview = Rc::new(Previewer { server_notifier: server_notifier.clone(), to_show: Default::default(), + show_preview_ui: RefCell::new(true), }); let init_param = serde_wasm_bindgen::from_value(init_param)?;