mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-22 16:22:17 +00:00
live preview: Implement resizing and moving of selected eleement
Much polish needed, but it is a basis to build upon.
This commit is contained in:
parent
e1aefc6f16
commit
08372e5a07
10 changed files with 427 additions and 44 deletions
|
@ -195,6 +195,19 @@ pub struct ComponentAddition {
|
|||
pub component_text: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct PropertyChange {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl PropertyChange {
|
||||
pub fn new(name: &str, value: String) -> Self {
|
||||
PropertyChange { name: name.to_string(), value }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub enum PreviewToLspMessage {
|
||||
|
@ -204,6 +217,7 @@ pub enum PreviewToLspMessage {
|
|||
PreviewTypeChanged { is_external: bool },
|
||||
RequestState { unused: bool }, // send all documents!
|
||||
AddComponent { label: Option<String>, component: ComponentAddition },
|
||||
UpdateElement { position: VersionedPosition, properties: Vec<PropertyChange> },
|
||||
}
|
||||
|
||||
/// Information on the Element types available
|
||||
|
|
|
@ -1419,6 +1419,45 @@ pub fn add_component(
|
|||
.ok_or("Could not create workspace edit".into())
|
||||
}
|
||||
|
||||
pub fn update_element(
|
||||
ctx: &Context,
|
||||
position: crate::common::VersionedPosition,
|
||||
properties: Vec<crate::common::PropertyChange>,
|
||||
) -> Result<lsp_types::WorkspaceEdit> {
|
||||
let mut document_cache = ctx.document_cache.borrow_mut();
|
||||
let file = lsp_types::Url::to_file_path(position.url())
|
||||
.map_err(|_| "Failed to convert URL to file path".to_string())?;
|
||||
|
||||
if &document_cache.document_version(position.url()) != position.version() {
|
||||
return Err("Document version mismatch.".into());
|
||||
}
|
||||
|
||||
let doc = document_cache
|
||||
.documents
|
||||
.get_document(&file)
|
||||
.ok_or_else(|| "Document not found".to_string())?;
|
||||
|
||||
let source_file = doc
|
||||
.node
|
||||
.as_ref()
|
||||
.map(|n| n.source_file.clone())
|
||||
.ok_or_else(|| "Document had no node".to_string())?;
|
||||
let element_position = map_position(&source_file, position.offset().into());
|
||||
|
||||
let element = element_at_position(&mut document_cache, &position.url(), &element_position)
|
||||
.ok_or_else(|| {
|
||||
format!("No element found at the given start position {:?}", &element_position)
|
||||
})?;
|
||||
|
||||
let (_, e) = crate::language::properties::set_bindings(
|
||||
&mut document_cache,
|
||||
position.url(),
|
||||
&element,
|
||||
&properties,
|
||||
)?;
|
||||
Ok(e.ok_or_else(|| "Failed to create workspace edit".to_string())?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -677,6 +677,47 @@ pub(crate) fn set_binding(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_bindings(
|
||||
document_cache: &mut DocumentCache,
|
||||
uri: &lsp_types::Url,
|
||||
element: &ElementRc,
|
||||
properties: &[crate::common::PropertyChange],
|
||||
) -> Result<(SetBindingResponse, Option<lsp_types::WorkspaceEdit>)> {
|
||||
let version = document_cache.document_version(uri);
|
||||
let (responses, edits) = properties
|
||||
.iter()
|
||||
.map(|p| set_binding(document_cache, uri, element, &p.name, p.value.clone()))
|
||||
.fold(
|
||||
Ok((SetBindingResponse { diagnostics: Default::default() }, Vec::new())),
|
||||
|prev_result: Result<(SetBindingResponse, Vec<lsp_types::TextEdit>)>, next_result| {
|
||||
let (mut responses, mut edits) = prev_result?;
|
||||
let (nr, ne) = next_result?;
|
||||
|
||||
responses.diagnostics.extend_from_slice(&nr.diagnostics);
|
||||
|
||||
match ne {
|
||||
Some(lsp_types::WorkspaceEdit {
|
||||
document_changes: Some(lsp_types::DocumentChanges::Edits(e)),
|
||||
..
|
||||
}) => {
|
||||
edits.extend(e.get(0).unwrap().edits.iter().filter_map(|e| match e {
|
||||
lsp_types::OneOf::Left(edit) => Some(edit.clone()),
|
||||
_ => None,
|
||||
}));
|
||||
}
|
||||
_ => { /* do nothing */ }
|
||||
};
|
||||
|
||||
Ok((responses, edits))
|
||||
},
|
||||
)?;
|
||||
if edits.is_empty() {
|
||||
Ok((responses, None))
|
||||
} else {
|
||||
Ok((responses, Some(crate::common::create_workspace_edit(uri.clone(), version, edits))))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_workspace_edit_for_remove_binding(
|
||||
uri: &lsp_types::Url,
|
||||
version: SourceFileVersion,
|
||||
|
|
|
@ -440,7 +440,13 @@ async fn handle_preview_to_lsp_message(
|
|||
crate::language::request_state(ctx);
|
||||
}
|
||||
M::AddComponent { label, component } => {
|
||||
let edit = crate::language::add_component(ctx, component)?;
|
||||
let edit = match crate::language::add_component(ctx, component) {
|
||||
Ok(edit) => edit,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let response = ctx
|
||||
.server_notifier
|
||||
.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
||||
|
@ -454,6 +460,30 @@ async fn handle_preview_to_lsp_message(
|
|||
.into());
|
||||
}
|
||||
}
|
||||
M::UpdateElement { position, properties } => {
|
||||
let edit = match crate::language::update_element(ctx, position, properties) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let response = ctx
|
||||
.server_notifier
|
||||
.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
||||
lsp_types::ApplyWorkspaceEditParams {
|
||||
label: Some("Element update".to_string()),
|
||||
edit,
|
||||
},
|
||||
)?
|
||||
.await?;
|
||||
if !response.applied {
|
||||
return Err(response
|
||||
.failure_reason
|
||||
.unwrap_or("Operation failed, no specific reason given".into())
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use crate::common::{
|
||||
ComponentInformation, PreviewComponent, PreviewConfig, UrlVersion, VersionedUrl,
|
||||
ComponentInformation, PreviewComponent, PreviewConfig, UrlVersion, VersionedPosition,
|
||||
VersionedUrl,
|
||||
};
|
||||
use crate::lsp_ext::Health;
|
||||
use crate::preview::element_selection::ElementSelection;
|
||||
|
@ -124,6 +125,64 @@ fn drop_component(
|
|||
};
|
||||
}
|
||||
|
||||
// triggered from the UI, running in UI thread
|
||||
fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32) {
|
||||
let Some(selected) = PREVIEW_STATE.with(move |preview_state| {
|
||||
let preview_state = preview_state.borrow();
|
||||
preview_state.selected.clone()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(selected_element) = selected_element() else {
|
||||
return;
|
||||
};
|
||||
let Some(component_instance) = component_instance() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(geometry) = component_instance
|
||||
.element_position(&selected_element)
|
||||
.get(selected.instance_index)
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let properties = {
|
||||
let mut p = Vec::with_capacity(4);
|
||||
if geometry.origin.x != x {
|
||||
p.push(crate::common::PropertyChange::new("x", format!("{}px", x.round())));
|
||||
}
|
||||
if geometry.origin.y != y {
|
||||
p.push(crate::common::PropertyChange::new("y", format!("{}px", y.round())));
|
||||
}
|
||||
if geometry.size.width != width {
|
||||
p.push(crate::common::PropertyChange::new("width", format!("{}px", width.round())));
|
||||
}
|
||||
if geometry.size.height != height {
|
||||
p.push(crate::common::PropertyChange::new("height", format!("{}px", height.round())));
|
||||
}
|
||||
p
|
||||
};
|
||||
|
||||
if !properties.is_empty() {
|
||||
let Ok(url) = Url::from_file_path(&selected.path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
let Some((version, _)) = cache.source_code.get(&url).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_message_to_lsp(crate::common::PreviewToLspMessage::UpdateElement {
|
||||
position: VersionedPosition::new(VersionedUrl::new(url, version), selected.offset),
|
||||
properties,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn change_style() {
|
||||
let cache = CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
|
||||
let ui_is_visible = cache.ui_is_visible;
|
||||
|
@ -515,6 +574,7 @@ fn set_selections(
|
|||
x: g.origin.x,
|
||||
y: g.origin.y,
|
||||
border_color: if i == main_index { border_color } else { secondary_border_color },
|
||||
is_primary: i == main_index,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let model = Rc::new(slint::VecModel::from(values));
|
||||
|
|
|
@ -45,6 +45,7 @@ pub fn create_ui(style: String, experimental: bool) -> Result<PreviewUi, Platfor
|
|||
ui.on_select_behind(super::element_selection::select_element_behind);
|
||||
ui.on_can_drop(super::can_drop_component);
|
||||
ui.on_drop(super::drop_component);
|
||||
ui.on_selected_element_update_geometry(super::change_geometry_of_selected_element);
|
||||
|
||||
Ok(ui)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,56 @@ export struct Selection {
|
|||
width: length,
|
||||
height: length,
|
||||
border-color: color,
|
||||
is-primary: bool,
|
||||
}
|
||||
|
||||
component SelectionFrame {
|
||||
in property <Selection> selection;
|
||||
|
||||
x: root.selection.x;
|
||||
y: root.selection.y;
|
||||
width: root.selection.width;
|
||||
height: root.selection.height;
|
||||
|
||||
callback update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||
|
||||
if !selection.is-primary: Rectangle {
|
||||
x: 0;
|
||||
y: 0;
|
||||
border-color: root.selection.border-color;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
if selection.is-primary: Resizer {
|
||||
is-moveable: true;
|
||||
is-resizable: true;
|
||||
|
||||
x-position: root.x;
|
||||
y-position: root.y;
|
||||
|
||||
color: root.selection.border-color;
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
|
||||
update-geometry(x, y, w, h, done) => {
|
||||
root.x = x;
|
||||
root.y = y;
|
||||
root.width = w;
|
||||
root.height = h;
|
||||
|
||||
if done {
|
||||
root.update-geometry(x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
inner := Rectangle {
|
||||
border-color: root.selection.border-color;
|
||||
border-width: 1px;
|
||||
background: parent.resizing || parent.moving ? root.selection.border-color.with-alpha(0.5) : root.selection.border-color.with-alpha(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component DrawArea {
|
||||
|
@ -38,6 +88,7 @@ export component DrawArea {
|
|||
callback select-behind(/* x */ length, /* y */ length, /* enter_component? */ bool, /* reverse */ bool);
|
||||
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
||||
callback unselect();
|
||||
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||
|
||||
preferred-height: max(i-preview-area-container.preferred-height, i-preview-area-container.min-height) + 2 * i-scroll-view.border;
|
||||
preferred-width: max(i-preview-area-container.preferred-width, i-preview-area-container.min-width) + 2 * i-scroll-view.border;
|
||||
|
@ -51,8 +102,8 @@ export component DrawArea {
|
|||
i-drawing-rect := Rectangle {
|
||||
background: Palette.alternate-background;
|
||||
|
||||
width: max(i-scroll-view.visible-width, i-resizer.width + i-scroll-view.border);
|
||||
height: max(i-scroll-view.visible-height, i-resizer.height + i-scroll-view.border);
|
||||
width: max(i-scroll-view.visible-width, main-resizer.width + i-scroll-view.border);
|
||||
height: max(i-scroll-view.visible-height, main-resizer.height + i-scroll-view.border);
|
||||
|
||||
unselect-area := TouchArea {
|
||||
clicked => { root.unselect(); }
|
||||
|
@ -61,18 +112,22 @@ export component DrawArea {
|
|||
}
|
||||
|
||||
i-content-border := Rectangle {
|
||||
x: i-resizer.x + (i-resizer.width - self.width) / 2;
|
||||
y: i-resizer.y + (i-resizer.height - self.height) / 2;
|
||||
width: i-resizer.width + 2 * self.border-width;
|
||||
height: i-resizer.height + 2 * self.border-width;
|
||||
x: main-resizer.x + (main-resizer.width - self.width) / 2;
|
||||
y: main-resizer.y + (main-resizer.height - self.height) / 2;
|
||||
width: main-resizer.width + 2 * self.border-width;
|
||||
height: main-resizer.height + 2 * self.border-width;
|
||||
border-width: 1px;
|
||||
border-color: Palette.border;
|
||||
}
|
||||
|
||||
i-resizer := Resizer {
|
||||
main-resizer := Resizer {
|
||||
is-moveable: false;
|
||||
is-resizable <=> i-preview-area-container.is-resizable;
|
||||
|
||||
resize(w, h) => {
|
||||
x-position: parent.x;
|
||||
y-position: parent.y;
|
||||
|
||||
update-geometry(_, _, w, h) => {
|
||||
i-preview-area-container.width = clamp(w, i-preview-area-container.min-width, i-preview-area-container.max-width);
|
||||
i-preview-area-container.height = clamp(h, i-preview-area-container.min-height, i-preview-area-container.max-height);
|
||||
}
|
||||
|
@ -93,7 +148,6 @@ export component DrawArea {
|
|||
}
|
||||
|
||||
i-preview-area-container := ComponentContainer {
|
||||
|
||||
property <bool> is-resizable: (self.min-width != self.max-width && self.min-height != self.max-height) && self.has-component;
|
||||
|
||||
component-factory <=> root.preview-area;
|
||||
|
@ -103,6 +157,7 @@ export component DrawArea {
|
|||
// Instead, we use a init function to initialize
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
|
||||
init => {
|
||||
self.width = max(self.preferred-width, self.min-width);
|
||||
self.height = max(self.preferred-height, self.min-height);
|
||||
|
@ -141,13 +196,11 @@ export component DrawArea {
|
|||
}
|
||||
|
||||
i-selection-display-area := Rectangle {
|
||||
for s in root.selections: Rectangle {
|
||||
x: s.x;
|
||||
y: s.y;
|
||||
width: s.width;
|
||||
height: s.height;
|
||||
border-color: s.border-color;
|
||||
border-width: 1px;
|
||||
for s in root.selections: SelectionFrame {
|
||||
selection: s;
|
||||
update-geometry(x, y, w, h) => {
|
||||
root.selected-element-update-geometry(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export component PreviewUi inherits Window {
|
|||
|
||||
pure callback can-drop(/* component_type */ string, /* x */ length, /* y */ length) -> bool;
|
||||
callback drop(/* component_type */ string, /* import_path */ string, /* is_layout */ bool, /* x */ length, /* y */ length);
|
||||
callback selected-element-update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length);
|
||||
callback select-at(/* x */ length, /* y */ length, /* enter_component? */ bool);
|
||||
callback select-behind(/* x */ length, /* y */ length, /* enter_component* */ bool, /* reverse */ bool);
|
||||
callback show-document(/* url */ string, /* line */ int, /* column */ int);
|
||||
|
@ -115,6 +116,7 @@ export component PreviewUi inherits Window {
|
|||
selections <=> root.selections;
|
||||
|
||||
select-at(x, y, enter_component) => { root.select-at(x, y, enter_component); }
|
||||
selected-element-update-geometry(x, y, w, h) => { root.selected-element-update-geometry(x, y, w, h); }
|
||||
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(); }
|
||||
|
|
|
@ -6,8 +6,17 @@ global ResizeState {
|
|||
}
|
||||
|
||||
component ResizeHandle inherits Rectangle {
|
||||
callback resize(/* width */ length, /* height */ length);
|
||||
in property <MouseCursor> mouse-cursor;
|
||||
out property <bool> resizing <=> ta.pressed;
|
||||
|
||||
callback resize(/* width */ length, /* height */ length, /* done? */ bool);
|
||||
callback resize-done(/* width */ length, /* height */ length);
|
||||
|
||||
// Internal!
|
||||
in-out property <length> new-width;
|
||||
in-out property <length> new-height;
|
||||
in-out property <length> new-x;
|
||||
in-out property <length> new-y;
|
||||
|
||||
width: ResizeState.handle-size;
|
||||
height: ResizeState.handle-size;
|
||||
|
@ -16,74 +25,185 @@ component ResizeHandle inherits Rectangle {
|
|||
border-color: Colors.white;
|
||||
border-width: 1px;
|
||||
|
||||
TouchArea {
|
||||
moved() => {
|
||||
root.resize(self.mouse-x - self.pressed-x, self.mouse-y - self.pressed-y);
|
||||
}
|
||||
ta := TouchArea {
|
||||
private property <length> x-offset: self.mouse-x - self.pressed-x;
|
||||
private property <length> y-offset: self.mouse-y - self.pressed-y;
|
||||
|
||||
mouse-cursor <=> root.mouse-cursor;
|
||||
|
||||
moved() => {
|
||||
root.resize(x-offset, y-offset, false);
|
||||
}
|
||||
|
||||
pointer-event(event) => {
|
||||
if event.button == PointerEventButton.left && event.kind == PointerEventKind.up {
|
||||
root.resize(x-offset, y-offset, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component Resizer {
|
||||
in property <bool> is-resizable: true;
|
||||
callback resize(/* width */ length, /* height */ length);
|
||||
in property <length> x-position;
|
||||
in property <length> y-position;
|
||||
|
||||
property <length> handle-size: ResizeState.handle-size;
|
||||
in property <bool> is-resizable: true;
|
||||
in property <bool> is-moveable: false;
|
||||
in property <color> color: Colors.black;
|
||||
out property <bool> resizing: n.resizing || ne.resizing || e.resizing || se.resizing || s.resizing || sw.resizing || w.resizing || nw.resizing;
|
||||
out property <bool> moving: resize-area.moving;
|
||||
out property <length> handle-size: ResizeState.handle-size;
|
||||
|
||||
callback update-geometry(/* x */ length, /* y */ length, /* width */ length, /* height */ length, /* done */ bool);
|
||||
|
||||
// Private!
|
||||
in-out property <length> top: self.y-position;
|
||||
in-out property <length> bottom: self.y-position + self.height;
|
||||
in-out property <length> left: self.x-position;
|
||||
in-out property <length> right: self.x-position + self.width;
|
||||
|
||||
resize-area := Rectangle {
|
||||
width <=> parent.width;
|
||||
height <=> parent.height;
|
||||
in-out property <bool> moving: false;
|
||||
|
||||
width <=> root.width;
|
||||
height <=> root.height;
|
||||
|
||||
@children
|
||||
|
||||
if root.is-moveable: TouchArea {
|
||||
private property <length> x-offset: self.mouse-x - self.pressed-x;
|
||||
private property <length> y-offset: self.mouse-y - self.pressed-y;
|
||||
private property <bool> has-moved;
|
||||
|
||||
mouse-cursor: MouseCursor.move;
|
||||
|
||||
moved() => {
|
||||
root.update-geometry(root.x-position + x-offset, root.y-position + y-offset, root.width, root.height, false);
|
||||
self.has-moved = true;
|
||||
}
|
||||
|
||||
pointer-event(event) => {
|
||||
resize-area.moving = self.pressed;
|
||||
if self.has-moved && event.button == PointerEventButton.left && event.kind == PointerEventKind.up {
|
||||
root.update-geometry(root.x-position + x-offset, root.y-position + y-offset, root.width, root.height, true);
|
||||
self.has-moved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: is-resizable;
|
||||
|
||||
ResizeHandle { // N
|
||||
resize(x-offset, y-offset) => { root.resize(root.width, y-offset * -1.0 + root.height); }
|
||||
n := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = root.width;
|
||||
self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
|
||||
self.new-x = root.left;
|
||||
self.new-y = root.bottom - self.new-height;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
|
||||
mouse-cursor: MouseCursor.n-resize;
|
||||
x: (root.width - root.handle-size) / 2.0;
|
||||
y: -root.handle-size;
|
||||
}
|
||||
ResizeHandle { // NE
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset + root.width, y-offset * -1.0 + root.height); }
|
||||
ne := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset + root.width);
|
||||
self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
|
||||
self.new-x = root.left;
|
||||
self.new-y = root.bottom - self.new-height;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.ne-resize;
|
||||
x: root.width;
|
||||
y: -root.handle-size;
|
||||
}
|
||||
ResizeHandle { // E
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset + root.width, root.height); }
|
||||
e := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset + root.width);
|
||||
self.new-height = root.height;
|
||||
self.new-x = root.left;
|
||||
self.new-y = root.top;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.e-resize;
|
||||
x: root.width;
|
||||
y: (root.height - root.handle-size) / 2.0;
|
||||
}
|
||||
ResizeHandle { // SE
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset + root.width, y-offset + root.height); }
|
||||
se := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset + root.width);
|
||||
self.new-height = Math.max(0px, y-offset + root.height);
|
||||
self.new-x = root.left;
|
||||
self.new-y = root.top;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.se-resize;
|
||||
x: root.width;
|
||||
y: root.height;
|
||||
}
|
||||
ResizeHandle { // S
|
||||
resize(x-offset, y-offset) => { root.resize(root.width, y-offset + root.height); }
|
||||
s := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = root.width;
|
||||
self.new-height = Math.max(0px, y-offset + root.height);
|
||||
self.new-x = root.left;
|
||||
self.new-y = root.top;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.s-resize;
|
||||
x: (root.width - root.handle-size) / 2.0;
|
||||
y: root.height;
|
||||
}
|
||||
ResizeHandle { // SW
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset * -1.0 + root.width, y-offset + root.height); }
|
||||
sw := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
|
||||
self.new-height = Math.max(0px, y-offset + root.height);
|
||||
self.new-x = root.right - self.new-width;
|
||||
self.new-y = root.top;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.sw-resize;
|
||||
x: -root.handle-size;
|
||||
y: root.height;
|
||||
}
|
||||
ResizeHandle { // W
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset * -1.0 + root.width, root.height); }
|
||||
w := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
|
||||
self.new-height = root.height;
|
||||
self.new-x = root.right - self.new-width;
|
||||
self.new-y = root.top;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.w-resize;
|
||||
x: -root.handle-size;
|
||||
y: (root.height - root.handle-size) / 2.0;
|
||||
}
|
||||
ResizeHandle { // NW
|
||||
resize(x-offset, y-offset) => { root.resize(x-offset * -1.0 + root.width, y-offset * -1.0 + root.height); }
|
||||
nw := ResizeHandle {
|
||||
background: root.color;
|
||||
resize(x-offset, y-offset, done) => {
|
||||
self.new-width = Math.max(0px, x-offset * -1.0 + root.width);
|
||||
self.new-height = Math.max(0px, y-offset * -1.0 + root.height);
|
||||
self.new-x = root.right - self.new-width;
|
||||
self.new-y = root.bottom - self.new-height;
|
||||
|
||||
root.update-geometry(self.new-x, self.new-y, self.new-width, self.new-height, done);
|
||||
}
|
||||
mouse-cursor: MouseCursor.nw-resize;
|
||||
x: -root.handle-size;
|
||||
y: -root.handle-size;
|
||||
|
|
|
@ -291,6 +291,29 @@ impl SlintServer {
|
|||
});
|
||||
}
|
||||
}
|
||||
M::UpdateElement { position, properties } => {
|
||||
let edit = crate::language::update_element(&self.ctx, position, properties);
|
||||
let sn = self.ctx.server_notifier.clone();
|
||||
|
||||
if let Ok(edit) = edit {
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let fut = sn.send_request::<lsp_types::request::ApplyWorkspaceEdit>(
|
||||
lsp_types::ApplyWorkspaceEditParams {
|
||||
label: Some("Element update".to_string()),
|
||||
edit,
|
||||
},
|
||||
);
|
||||
if let Ok(fut) = fut {
|
||||
// We ignore errors: If the LSP can not be reached, then all is lost
|
||||
// anyway. The other thing that might go wrong is that our Workspace Edit
|
||||
// refers to some outdated text. In that case the update is most likely
|
||||
// in flight already and will cause the preview to re-render, which also
|
||||
// invalidates all our state
|
||||
let _ = fut.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue