live-preview: Refactor Api between backend and UI

Only push basic information over the boundary in a model.
The values themselves now need to be queried via `Api`.

Pros:
 * Slint tracks the properties automatically, no more
   Property change tracker!

Cons:
 * Live preview is less useful now as we can not yet provide
   function call results via ourr data API.
This commit is contained in:
Tobias Hunger 2025-02-20 15:10:49 +00:00 committed by Tobias Hunger
parent fd0f6e822a
commit f96ce07287
5 changed files with 252 additions and 460 deletions

View file

@ -16,7 +16,7 @@ use lsp_types::Url;
use slint::PlatformError;
use slint_interpreter::{ComponentDefinition, ComponentHandle, ComponentInstance};
use std::borrow::BorrowMut;
use std::cell::{OnceCell, RefCell};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::rc::Rc;
@ -126,9 +126,6 @@ struct PreviewState {
workspace_edit_sent: bool,
known_components: Vec<ComponentInformation>,
preview_loading_delay_timer: Option<slint::Timer>,
property_change_tracker: OnceCell<
std::pin::Pin<Box<i_slint_core::properties::PropertyTracker<PreviewDataDirtyHandler>>>,
>,
}
impl PreviewState {
@ -935,40 +932,6 @@ fn extract_resources(
result
}
struct PreviewDataDirtyHandler {}
impl i_slint_core::properties::PropertyDirtyHandler for PreviewDataDirtyHandler {
fn notify(self: std::pin::Pin<&Self>) {
PREVIEW_STATE.with(|preview_state| {
let mut preview_state = preview_state.borrow_mut();
let preview_data = &track_preview_data(&mut preview_state);
if let Some(ui) = &preview_state.ui {
ui::ui_set_preview_data(ui, preview_data);
}
});
}
}
fn track_preview_data(
preview_state: &mut std::cell::RefMut<PreviewState>,
) -> HashMap<preview_data::PropertyContainer, Vec<preview_data::PreviewData>> {
let Some(component_instance) = preview_state.component_instance() else {
return Default::default();
};
let mut tracker = preview_state.property_change_tracker.get_or_init(move || {
Box::pin(i_slint_core::properties::PropertyTracker::new_with_dirty_handler(
PreviewDataDirtyHandler {},
))
});
tracker.borrow_mut().as_ref().evaluate_as_dependency_root(|| {
preview_data::query_preview_data_properties_and_callbacks(&component_instance)
})
}
fn finish_parsing(preview_url: &Url) {
set_status_text("");
@ -1039,12 +1002,17 @@ fn finish_parsing(preview_url: &Url) {
preview_state.document_cache.borrow_mut().replace(Some(Rc::new(document_cache)));
let preview_data = track_preview_data(&mut preview_state);
let preview_data = preview_state
.component_instance()
.map(|component_instance| {
preview_data::query_preview_data_properties_and_callbacks(&component_instance)
})
.unwrap_or_default();
if let Some(ui) = &preview_state.ui {
ui::ui_set_uses_widgets(ui, uses_widgets);
ui::ui_set_known_components(ui, &preview_state.known_components, index);
ui::ui_set_preview_data(ui, &preview_data);
ui::ui_set_preview_data(ui, preview_data);
}
});
}

View file

@ -67,6 +67,46 @@ impl PreviewData {
}
}
pub fn get_preview_data(
component_instance: &ComponentInstance,
container: PropertyContainer,
property_name: String,
) -> Option<PreviewData> {
fn find_preview_data(
property_name: &str,
mut it: impl Iterator<
Item = (
String,
(
i_slint_compiler::langtype::Type,
i_slint_compiler::object_tree::PropertyVisibility,
),
),
>,
value_query: &dyn Fn(&str) -> Option<slint_interpreter::Value>,
) -> Option<PreviewData> {
it.find(|(name, (_, _))| name == property_name).map(|(name, (ty, visibility))| {
let value = value_query(&name);
PreviewData { name, ty, visibility, value }
})
}
let definition = &component_instance.definition();
match &container {
PropertyContainer::Main => {
find_preview_data(&property_name, &mut definition.properties_and_callbacks(), &|name| {
component_instance.get_property(name).ok()
})
}
PropertyContainer::Global(g) => find_preview_data(
&property_name,
&mut definition.global_properties_and_callbacks(g)?,
&|name| component_instance.get_global_property(g, name).ok(),
),
}
}
pub fn query_preview_data_properties_and_callbacks(
component_instance: &ComponentInstance,
) -> HashMap<PropertyContainer, Vec<PreviewData>> {
@ -147,38 +187,6 @@ fn find_component_properties_and_callbacks<'a>(
}
}
pub fn set_preview_data(
component_instance: &ComponentInstance,
container: PropertyContainer,
property_name: String,
values: Vec<Vec<String>>,
) -> Result<(), String> {
let definition = &component_instance.definition();
let (_, (ty, _)) = find_component_properties_and_callbacks(definition, &container)?
.find(|(name, (_, _))| name == &property_name)
.ok_or_else(|| {
format!("Property name {property_name} not found on component {container}")
})?;
if values.len() == 1 && values[0].len() == 1 {
let json_value: serde_json::Value = serde_json::from_str(&values[0][0])
.map_err(|e| format!("Failed to read value as JSON: {e}"))?;
let value = slint_interpreter::json::value_from_json(&ty, &json_value)?;
match &container {
PropertyContainer::Main => component_instance
.set_property(&property_name, value)
.map_err(|e| format!("Failed to set property: {e}"))?,
PropertyContainer::Global(g) => component_instance
.set_global_property(g, &property_name, value)
.map_err(|e| format!("Failed to set global property: {e}"))?,
}
}
Ok(())
}
pub fn set_json_preview_data(
component_instance: &ComponentInstance,
container: PropertyContainer,

View file

@ -97,7 +97,7 @@ pub fn create_ui(style: String, experimental: bool) -> Result<PreviewUi, Platfor
api.on_set_color_binding(super::set_color_binding);
api.on_property_declaration_ranges(super::property_declaration_ranges);
api.on_set_preview_data(set_preview_data);
api.on_get_property_value(get_property_value);
api.on_set_json_preview_data(set_json_preview_data);
api.on_string_to_code(string_to_code);
@ -794,16 +794,12 @@ fn update_grouped_properties(
to_do.push(Op::Remove(c_index));
cp = c_it.next();
}
(Some(c), Some(n)) => {
if c.name < n.name {
(Some(c), Some(n)) => match c.name.cmp(&n.name) {
std::cmp::Ordering::Less => {
to_do.push(Op::Remove(c_index));
cp = c_it.next();
} else if c.name > n.name {
to_do.push(Op::Insert((c_index, n_index)));
c_index += 1;
n_index += 1;
np = n_it.next();
} else {
}
std::cmp::Ordering::Equal => {
if !is_equal_property(c, n) {
to_do.push(Op::Copy((c_index, n_index)));
}
@ -812,7 +808,13 @@ fn update_grouped_properties(
cp = c_it.next();
np = n_it.next();
}
std::cmp::Ordering::Greater => {
to_do.push(Op::Insert((c_index, n_index)));
c_index += 1;
n_index += 1;
np = n_it.next();
}
},
(None, Some(_)) => {
to_do.push(Op::PushBack(n_index));
n_index += 1;
@ -1030,51 +1032,44 @@ fn map_value_and_type(
}
}
fn map_preview_data_property(rp: &preview_data::PreviewData) -> Option<PreviewData> {
if !rp.is_property() {
fn map_preview_data_to_property_value(
preview_data: &preview_data::PreviewData,
) -> Option<PropertyValue> {
let mut mapping = ValueMapping::default();
map_value_and_type(&preview_data.ty, &preview_data.value, &mut mapping);
mapping.array_values.first().and_then(|av| av.first()).cloned()
}
fn map_preview_data_property(preview_data: &preview_data::PreviewData) -> Option<PreviewData> {
if !preview_data.is_property() {
return None;
};
let has_getter = rp.has_getter();
let has_setter = rp.has_setter();
let has_getter = preview_data.has_getter();
let has_setter = preview_data.has_setter();
let mut mapping = ValueMapping::default();
map_value_and_type(&rp.ty, &rp.value, &mut mapping);
let array_values = mapping
.array_values
.drain(..)
.map(|v| Rc::new(slint::VecModel::from(v)).into())
.collect::<Vec<slint::ModelRc<_>>>();
let array_values = Rc::new(slint::VecModel::from(array_values)).into();
map_value_and_type(&preview_data.ty, &preview_data.value, &mut mapping);
Some(PreviewData {
name: rp.name.clone().into(),
name: preview_data.name.clone().into(),
has_getter,
has_setter,
prefer_json: mapping.is_too_complex,
is_array: mapping.is_array,
header: Rc::new(VecModel::from(
mapping.header.drain(..).map(slint::SharedString::from).collect::<Vec<_>>(),
))
.into(),
array_values,
kind: if mapping.is_too_complex { PreviewDataKind::Json } else { PreviewDataKind::Value },
})
}
pub fn ui_set_preview_data(
ui: &PreviewUi,
preview_data: &HashMap<preview_data::PropertyContainer, Vec<preview_data::PreviewData>>,
preview_data: HashMap<preview_data::PropertyContainer, Vec<preview_data::PreviewData>>,
) {
let mut result: Vec<PropertyContainer> = vec![];
fn fill_container(
container_name: String,
container_id: String,
properties: &[preview_data::PreviewData],
) -> Option<PropertyContainer> {
let properties =
properties.iter().filter_map(|rp| map_preview_data_property(rp)).collect::<Vec<_>>();
properties.iter().filter_map(map_preview_data_property).collect::<Vec<_>>();
(!properties.is_empty()).then(|| PropertyContainer {
container_name: container_name.into(),
@ -1083,11 +1078,14 @@ pub fn ui_set_preview_data(
})
}
let mut result: Vec<PropertyContainer> = vec![];
if let Some(main) = preview_data.get(&preview_data::PropertyContainer::Main) {
if let Some(c) = fill_container("<MAIN>".to_string(), String::new(), main) {
result.push(c)
}
}
for component_key in
preview_data.keys().filter(|k| **k != preview_data::PropertyContainer::Main)
{
@ -1101,85 +1099,31 @@ pub fn ui_set_preview_data(
let api = ui.global::<Api>();
let mut old_model = api.get_preview_data();
let old_model = api.get_preview_data();
fn update_model(
old_model: &mut slint::ModelRc<PropertyContainer>,
new_model: Vec<PropertyContainer>,
) -> Option<slint::ModelRc<PropertyContainer>> {
// The structure should never change as the API between business logic and UI should be pretty
// fixed.
fn m(model: Vec<PropertyContainer>) -> Option<slint::ModelRc<PropertyContainer>> {
Some(Rc::new(VecModel::from(model)).into())
}
fn is_semantic_equal(o: &PreviewData, n: &PreviewData) -> bool {
if o.name != n.name
|| o.has_getter != n.has_getter
|| o.has_setter != n.has_setter
|| o.is_array != n.is_array
|| o.prefer_json != n.prefer_json
{
return false;
}
if o.header.row_count() != n.header.row_count() {
return false;
}
for (oh, nh) in o.header.iter().zip(n.header.iter()) {
if oh != nh {
return false;
}
}
if o.array_values.row_count() != n.array_values.row_count() {
return false;
}
for (oav, nav) in o.array_values.iter().zip(n.array_values.iter()) {
if oav.row_count() != nav.row_count() {
return false;
}
for (oavv, navv) in oav.iter().zip(nav.iter()) {
if oavv != navv {
return false;
}
}
}
true
}
old_model: &slint::ModelRc<PropertyContainer>,
new_model: &[PropertyContainer],
) -> bool {
if old_model.row_count() != new_model.len() {
return m(new_model);
return true;
}
for (oc, nc) in old_model.iter().zip(new_model.iter()) {
if oc.container_name != nc.container_name
|| oc.container_id != nc.container_id
|| oc.properties.row_count() != nc.properties.row_count()
|| oc.properties.iter().zip(nc.properties.iter()).any(|(o, n)| o != n)
{
return m(new_model);
}
let mut to_replace = oc
.properties
.iter()
.zip(nc.properties.iter())
.enumerate()
.filter_map(|(i, (o, n))| (!is_semantic_equal(&o, &n)).then_some((i, n)))
.collect::<Vec<_>>();
for (i, n) in to_replace.drain(..) {
oc.properties.set_row_data(i, n);
return true;
}
}
None
false
}
if let Some(m) = update_model(&mut old_model, result) {
api.set_preview_data(m);
if update_model(&old_model, &result) {
api.set_preview_data(Rc::new(VecModel::from(result)).into());
}
}
@ -1191,60 +1135,45 @@ fn to_property_container(container: slint::SharedString) -> preview_data::Proper
}
}
fn set_preview_data(
container: SharedString,
property_name: SharedString,
model: slint::ModelRc<slint::ModelRc<PropertyValue>>,
) -> bool {
if model.row_count() == 0 || property_name.is_empty() {
return false;
}
let values = model
.iter()
.map(|r| {
r.iter()
.map(|c| if c.was_edited { c.edited_value.to_string() } else { c.code.to_string() })
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
if let Some(component_instance) = preview::component_instance() {
preview_data::set_preview_data(
fn get_property_value(container: SharedString, property_name: SharedString) -> PropertyValue {
preview::component_instance()
.and_then(|component_instance| {
preview_data::get_preview_data(
&component_instance,
to_property_container(container),
property_name.to_string(),
values,
)
.is_ok()
} else {
false
}
})
.and_then(|pd| map_preview_data_to_property_value(&pd))
.unwrap_or_else(Default::default)
}
fn set_json_preview_data(
container: SharedString,
property_name: SharedString,
json_string: SharedString,
) {
) -> bool {
let property_name = (!property_name.is_empty()).then_some(property_name.to_string());
let Ok(json) = serde_json::from_str::<serde_json::Value>(&json_string.to_string()) else {
return;
let Ok(json) = serde_json::from_str::<serde_json::Value>(json_string.as_ref()) else {
return false;
};
if property_name.is_none() && !json.is_object() {
return;
return false;
}
if let Some(component_instance) = preview::component_instance() {
let _ = preview_data::set_json_preview_data(
preview::component_instance()
.and_then(|component_instance| {
preview_data::set_json_preview_data(
&component_instance,
to_property_container(container),
property_name,
json,
);
};
)
.ok()
})
.is_some()
}
fn update_properties(
@ -1309,7 +1238,7 @@ mod tests {
use i_slint_core::model::Model;
use super::{map_preview_data_property, PropertyInformation, PropertyValue, PropertyValueKind};
use super::{PropertyInformation, PropertyValue, PropertyValueKind};
fn properties_at_position(
source: &str,
@ -1945,46 +1874,23 @@ export component Tester {{
type_def: &str,
type_name: &str,
code: &str,
expected: super::PreviewData,
expected_data: super::PreviewData,
expected_value: super::PropertyValue,
) {
let rp = generate_preview_data(visibility, type_def, type_name, code);
let raw_data = generate_preview_data(visibility, type_def, type_name, code);
let rp = map_preview_data_property(&rp).unwrap();
let rp = super::map_preview_data_property(&raw_data).unwrap();
eprintln!("*** Validating PreviewData: Received: {rp:?}");
eprintln!("*** Validating PreviewData: Expected: {expected:?}");
eprintln!("*** Validating PreviewData: Expected: {expected_data:?}");
assert_eq!(rp.name, expected.name);
assert_eq!(rp.has_getter, expected.has_getter);
assert_eq!(rp.has_setter, expected.has_setter);
assert_eq!(rp.is_array, expected.is_array);
assert_eq!(rp.prefer_json, expected.prefer_json);
assert_eq!(rp.name, expected_data.name);
assert_eq!(rp.has_getter, expected_data.has_getter);
assert_eq!(rp.has_setter, expected_data.has_setter);
assert_eq!(rp.kind, expected_data.kind);
eprintln!("*** Basic properties all match, looking at values next...");
eprintln!("*** Headers: Received:");
for h in rp.header.iter() {
eprintln!("*** {h}.");
}
eprintln!("*** Headers: Expected:");
for h in expected.header.iter() {
eprintln!("*** {h}.");
}
assert_eq!(rp.header.row_count(), expected.header.row_count());
for (r, e) in rp.header.iter().zip(expected.header.iter()) {
assert_eq!(r, e);
}
eprintln!("*** Values all match, looking at array_values next...");
assert_eq!(rp.array_values.row_count(), expected.array_values.row_count());
for (rr, er) in rp.array_values.iter().zip(expected.array_values.iter()) {
assert_eq!(rr.row_count(), er.row_count());
for (e, r) in rr.iter().zip(er.iter()) {
compare_pv(&r, &e);
}
}
let pv = super::map_preview_data_to_property_value(&raw_data).unwrap();
compare_pv(&pv, &expected_value);
}
#[test]
@ -1997,18 +1903,14 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "\"Test\"".into(),
kind: super::PropertyValueKind::String,
value_string: "Test".into(),
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2023,9 +1925,10 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "100".into(),
kind: super::PropertyValueKind::Float,
value_float: 100.0,
@ -2041,11 +1944,6 @@ export component Tester {{
]))
.into(),
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2060,9 +1958,10 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "100000".into(),
kind: super::PropertyValueKind::Float,
value_float: 100000.0,
@ -2074,11 +1973,6 @@ export component Tester {{
.into(),
default_selection: 1,
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2093,9 +1987,10 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "36000".into(),
kind: super::PropertyValueKind::Float,
value_float: 36000.0,
@ -2108,11 +2003,6 @@ export component Tester {{
]))
.into(),
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2127,20 +2017,15 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "10".into(),
kind: super::PropertyValueKind::Float,
value_float: 10.0,
value_string: "10%".into(),
visual_items: std::rc::Rc::new(slint::VecModel::from(vec!["%".into()]))
.into(),
..Default::default()
}]),
)
.into()]))
.into(),
visual_items: std::rc::Rc::new(slint::VecModel::from(vec!["%".into()])).into(),
..Default::default()
},
);
@ -2156,9 +2041,10 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "\"#aabbccff\"".into(),
kind: super::PropertyValueKind::Color,
value_string: "#aabbccff".into(),
@ -2166,11 +2052,6 @@ export component Tester {{
0xff, 0xaa, 0xbb, 0xcc,
)),
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2185,19 +2066,15 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "12".into(),
kind: super::PropertyValueKind::Integer,
value_string: "12".into(),
value_int: 12,
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2212,19 +2089,15 @@ export component Tester {{
super::PreviewData {
name: "test".into(),
has_getter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "true".into(),
kind: super::PropertyValueKind::Boolean,
value_string: "true".into(),
value_bool: true,
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2240,19 +2113,15 @@ export component Tester {{
name: "test".into(),
has_getter: true,
has_setter: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PreviewDataKind::Value,
..Default::default()
},
super::PropertyValue {
code: "false".into(),
kind: super::PropertyValueKind::Boolean,
value_string: "false".into(),
value_bool: false,
..Default::default()
}]),
)
.into()]))
.into(),
..Default::default()
},
);
}
@ -2270,16 +2139,13 @@ export component Tester {{
name: "test".into(),
has_getter: true,
has_setter: true,
prefer_json: true,
header: std::rc::Rc::new(slint::VecModel::from(vec!["".into()])).into(),
array_values: std::rc::Rc::new(slint::VecModel::from(vec![std::rc::Rc::new(
slint::VecModel::from(vec![super::PropertyValue {
kind: super::PropertyValueKind::Code,
code: "{\n \"first\": [\n \"first of a kind\",\n \"second of a kind\"\n ]\n}".into(),
kind: super::PreviewDataKind::Json,
..Default::default()
},
])).into(),
]))
super::PropertyValue {
kind: super::PropertyValueKind::Code,
code:
"{\n \"first\": [\n \"first of a kind\",\n \"second of a kind\"\n ]\n}"
.into(),
..Default::default()
},

View file

@ -166,14 +166,16 @@ export struct PropertyGroup {
properties: [PropertyInformation],
}
export enum PreviewDataKind {
Value,
Json,
}
export struct PreviewData {
name: string,
has-getter: bool,
has-setter: bool,
is-array: bool,
prefer-json: bool,
header: [string], // "Simple" values + table headers
array-values: [[PropertyValue]], // The "Table values"
kind: PreviewDataKind,
}
/// Information on exported components and their properties
@ -406,54 +408,7 @@ export global Api {
// ## preview data
in-out property <[PropertyContainer]> preview-data: [
{
container-name: "Fruits",
properties: [
{
name: "AppleSetter",
has-getter: false,
has-setter: true,
is-array: false,
prefer-json: false,
header: [ "" ],
array-values: [[{
kind: PropertyValueKind.boolean,
value-bool: true,
}]],
},
{
name: "BananaGetter",
has-getter: true,
has-setter: true,
is-array: false,
prefer-json: true,
header: [ "fruit" ],
array-values: [[{
kind: PropertyValueKind.code,
code: "{\n json: true\n}"
}]]
},
]
},
{
container-name: "Vegetables",
properties: [
{
name: "Cucumber",
has-getter: true,
has-setter: true,
is-array: false,
prefer-json: false,
header: [ "" ],
array-values: [[{
kind: PropertyValueKind.string,
value-string: "a green banana",
}]],
}
]
},
];
in-out property <[PropertyContainer]> preview-data;
// # Callbacks
@ -517,8 +472,9 @@ export global Api {
pure callback string-to-code(value: string, is_translatable: bool, tr_context: string, tr_plural: string, tr_plural_expression: string) -> string;
// ## preview data
callback set-preview-data(component: string, name: string, raw-json-array-values: [[PropertyValue]]) -> bool;
callback set-json-preview-data(component: string, name: string, json-value: string);
pure callback get-property-value(component: string, name: string) -> PropertyValue;
callback set-json-preview-data(component: string, name: string, json-value: string) -> bool;
// Get the property declaration/definition ranges
callback property-declaration-ranges(property-name: string) -> PropertyDeclaration;

View file

@ -3,7 +3,7 @@
import { Button, CheckBox, ComboBox, LineEdit, Palette, Slider, TextEdit } from "std-widgets.slint";
import { Api, ColorData, ElementInformation, PreviewData, PropertyContainer, PropertyInformation, PropertyValue, PropertyValueKind } from "../api.slint";
import { Api, ColorData, ElementInformation, PreviewData, PreviewDataKind, PropertyContainer, PropertyInformation, PropertyValue, PropertyValueKind } from "../api.slint";
import { BodyStrongText } from "../components/body-strong-text.slint";
import { BodyText } from "../components/body-text.slint";
import { StateLayer } from "../components/state-layer.slint";
@ -889,6 +889,12 @@ export component PreviewDataPropertyValueWidget inherits VerticalLayout {
in property <PreviewData> preview-data;
in property <string> property-container-name;
private property <PropertyValue> value: Api.get-property-value(root.property-container-name, root.preview-data.name);
changed value => {
debug("\{self.property-container-name}.\{self.preview-data.name}: VALUE CHANGED TO \{self.value.code}");
}
callback edit-in-spreadsheet(rp: PropertyContainer);
function reset-action() {
@ -896,32 +902,20 @@ export component PreviewDataPropertyValueWidget inherits VerticalLayout {
}
function set-code-binding(text: string) -> bool {
root.value.was-edited = true;
root.value.edited-value = text;
root.array-values[0][0] = root.value;
return(Api.set-preview-data(root.property-container-name, root.preview-data.name, self.array-values));
return(Api.set-json-preview-data(root.property-container-name, root.preview-data.name, text));
}
private property <bool> is-simple: preview-data.header.length == 1 && !preview-data.is-array && !preview-data.prefer-json;
private property <bool> show-json: preview-data.prefer-json;
private property <[[PropertyValue]]> array-values: preview-data.array-values;
property <PropertyValue> value: root.preview-data.array-values[0][0];
if is-simple && value.kind == PropertyValueKind.code: CodeWidget {
if root.preview-data.kind == PreviewDataKind.Value && value.kind == PropertyValueKind.code: CodeWidget {
enabled: root.preview-data.has-setter;
property-name: root.preview-data.name;
property-value: value;
property-value <=> root.value;
has-code-action: false;
reset-action() => {
root.reset-action();
}
}
if is-simple && value.kind != PropertyValueKind.code: PropertyValueWidget {
if root.preview-data.kind == PreviewDataKind.Value && value.kind != PropertyValueKind.code: PropertyValueWidget {
property-value <=> root.value;
property-name: root.preview-data.name;
enabled: root.preview-data.has-setter;
@ -951,10 +945,10 @@ export component PreviewDataPropertyValueWidget inherits VerticalLayout {
return(root.set-code-binding(is_translated ? "\"\{text}\"" : text));
}
}
if show-json: EditJsonWidget {
if root.preview-data.kind == PreviewDataKind.Json: EditJsonWidget {
enabled: root.preview-data.has-setter;
property-name: root.preview-data.name;
property-value: value;
property-value <=> root.value;
set-code-binding(text) => {
root.set-code-binding(text);