mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-17 22:08:39 +00:00

* Tools: Remove the unecessary 'FF' from generated colors When colors in most UI tools are 100% alpha the extra 'FF' is auto cut off for readability. e.g. #000000FF becomes #000000. Since introducing the new color pickers this has been the case on the UI side. However the code generator for gradients was inconsistent and would still add in the unnecessary 'FF'. This fixes that and makes generated gradient code tidier. * Fix correct tests * Fix * [autofix.ci] apply automated fixes * Fix * Fix * Fix --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2158 lines
76 KiB
Rust
2158 lines
76 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
use std::{collections::HashMap, iter::once, rc::Rc};
|
|
|
|
use i_slint_compiler::parser::TextRange;
|
|
use i_slint_compiler::{expression_tree, langtype};
|
|
|
|
use itertools::Itertools;
|
|
use slint::{Model, ModelRc, SharedString, ToSharedString, VecModel};
|
|
use slint_interpreter::{DiagnosticLevel, PlatformError};
|
|
use smol_str::SmolStr;
|
|
|
|
use crate::common::{self, ComponentInformation};
|
|
use crate::preview::{self, preview_data, properties, SelectionNotification};
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use crate::wasm_prelude::*;
|
|
|
|
mod brushes;
|
|
pub mod log_messages;
|
|
pub mod palette;
|
|
mod property_view;
|
|
mod recent_colors;
|
|
pub mod search_model;
|
|
|
|
slint::include_modules!();
|
|
|
|
pub type PropertyDeclarations = HashMap<SmolStr, PropertyDeclaration>;
|
|
|
|
pub fn create_ui(
|
|
to_lsp: &Rc<dyn common::PreviewToLsp>,
|
|
style: &str,
|
|
experimental: bool,
|
|
) -> Result<PreviewUi, PlatformError> {
|
|
#[cfg(all(target_vendor = "apple", not(target_arch = "wasm32")))]
|
|
crate::preview::connector::native::init_apple_platform()?;
|
|
|
|
let ui = PreviewUi::new()?;
|
|
|
|
// styles:
|
|
let known_styles = once(&"native")
|
|
.chain(i_slint_compiler::fileaccess::styles().iter())
|
|
.filter(|s| s != &&"qt" || i_slint_backend_selector::HAS_NATIVE_STYLE)
|
|
.cloned()
|
|
.sorted()
|
|
.collect::<Vec<_>>();
|
|
let style = if known_styles.contains(&style) {
|
|
style.to_string()
|
|
} else {
|
|
known_styles
|
|
.iter()
|
|
.find(|x| **x == "native")
|
|
.or_else(|| known_styles.first())
|
|
.map(|s| s.to_string())
|
|
.unwrap_or_default()
|
|
};
|
|
|
|
let style_model = Rc::new({
|
|
let model = VecModel::default();
|
|
model.extend(known_styles.iter().map(|s| SharedString::from(*s)));
|
|
assert!(model.row_count() > 1);
|
|
model
|
|
});
|
|
|
|
let api = ui.global::<Api>();
|
|
|
|
api.set_current_style(style.clone().into());
|
|
api.set_experimental(experimental);
|
|
api.set_known_styles(style_model.into());
|
|
|
|
api.on_add_new_component(super::add_new_component);
|
|
api.on_rename_component(super::rename_component);
|
|
api.on_style_changed(super::change_style);
|
|
api.on_show_component(super::show_component);
|
|
|
|
let lsp = to_lsp.clone();
|
|
api.on_show_document(move |file, line, column| {
|
|
use lsp_types::{Position, Range};
|
|
let pos = Position::new((line as u32).saturating_sub(1), (column as u32).saturating_sub(1));
|
|
lsp.ask_editor_to_show_document(&file, Range::new(pos, pos), false).unwrap();
|
|
});
|
|
api.on_show_document_offset_range(super::show_document_offset_range);
|
|
api.on_show_preview_for(super::show_preview_for);
|
|
api.on_reload_preview(super::reload_preview);
|
|
api.on_unselect(super::element_selection::unselect_element);
|
|
api.on_reselect(super::element_selection::reselect_element);
|
|
api.on_select_at(super::element_selection::select_element_at);
|
|
api.on_selection_stack_at(super::element_selection::selection_stack_at);
|
|
api.on_filter_sort_selection_stack(super::element_selection::filter_sort_selection_stack);
|
|
api.on_find_selected_selection_stack_frame(|stack| {
|
|
stack.iter().find(|frame| frame.is_selected).unwrap_or_default()
|
|
});
|
|
api.on_select_element(|path, offset, x, y| {
|
|
super::element_selection::select_element_at_source_code_position(
|
|
PathBuf::from(path.to_string()),
|
|
preview::TextSize::from(offset as u32),
|
|
Some(i_slint_core::lengths::LogicalPoint::new(x, y)),
|
|
SelectionNotification::Now,
|
|
);
|
|
});
|
|
api.on_select_behind(super::element_selection::select_element_behind);
|
|
api.on_highlight_positions(super::element_selection::highlight_positions);
|
|
let lsp = to_lsp.clone();
|
|
api.on_can_drop(super::can_drop_component);
|
|
api.on_drop(move |component_index: i32, x: f32, y: f32| {
|
|
lsp.send_telemetry(&mut [(
|
|
"type".to_string(),
|
|
serde_json::to_value("component_dropped").unwrap(),
|
|
)])
|
|
.unwrap();
|
|
super::drop_component(component_index, x, y)
|
|
});
|
|
api.on_selected_element_resize(super::resize_selected_element);
|
|
api.on_selected_element_can_move_to(super::can_move_selected_element);
|
|
api.on_selected_element_move(super::move_selected_element);
|
|
api.on_selected_element_delete(super::delete_selected_element);
|
|
|
|
api.on_test_code_binding(super::test_code_binding);
|
|
api.on_set_code_binding(super::set_code_binding);
|
|
api.on_set_color_binding(super::set_color_binding);
|
|
api.on_set_element_id(super::set_element_id);
|
|
api.on_property_declaration_ranges(super::property_declaration_ranges);
|
|
|
|
api.on_get_property_value(get_property_value);
|
|
api.on_get_property_value_table(get_property_value_table);
|
|
api.on_set_property_value_table(set_property_value_table);
|
|
api.on_insert_row_into_value_table(insert_row_into_value_table);
|
|
api.on_remove_row_from_value_table(remove_row_from_value_table);
|
|
|
|
let lsp = to_lsp.clone();
|
|
api.on_set_json_preview_data(move |container, property_name, json_string, send_telemetry| {
|
|
if send_telemetry {
|
|
lsp.send_telemetry(&mut [(
|
|
"type".to_string(),
|
|
serde_json::to_value("data_json_changed").unwrap(),
|
|
)])
|
|
.unwrap();
|
|
}
|
|
set_json_preview_data(container, property_name, json_string)
|
|
});
|
|
|
|
api.on_string_to_code(string_to_code);
|
|
|
|
brushes::setup(&ui);
|
|
log_messages::setup(&ui);
|
|
palette::setup(&ui);
|
|
recent_colors::setup(&ui);
|
|
super::outline::setup(&ui);
|
|
super::undo_redo::setup(&ui);
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
api.set_control_key_name("command".into());
|
|
|
|
#[cfg(target_family = "wasm")]
|
|
if web_sys::window()
|
|
.and_then(|window| window.navigator().platform().ok())
|
|
.map_or(false, |platform| platform.to_ascii_lowercase().contains("mac"))
|
|
{
|
|
api.set_control_key_name("command".into());
|
|
}
|
|
|
|
Ok(ui)
|
|
}
|
|
|
|
fn extract_definition_location(ci: &ComponentInformation) -> (SharedString, SharedString) {
|
|
let Some(url) = ci.defined_at.as_ref().map(|da| da.url()) else {
|
|
return (Default::default(), Default::default());
|
|
};
|
|
|
|
let path = url.to_file_path().unwrap_or_default();
|
|
let file_name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
|
|
|
(url.to_string().into(), file_name.into())
|
|
}
|
|
|
|
pub fn ui_set_uses_widgets(ui: &PreviewUi, uses_widgets: bool) {
|
|
let api = ui.global::<Api>();
|
|
api.set_uses_widgets(uses_widgets);
|
|
}
|
|
|
|
pub fn set_diagnostics(ui: &PreviewUi, diagnostics: &[slint_interpreter::Diagnostic]) {
|
|
let summary = diagnostics
|
|
.iter()
|
|
.inspect(|d| {
|
|
let location = d.source_file().map(|p| {
|
|
let (line, column) = d.line_column();
|
|
(p.to_string_lossy().to_string().into(), line, column)
|
|
});
|
|
|
|
let level = match d.level() {
|
|
DiagnosticLevel::Error => LogMessageLevel::Error,
|
|
DiagnosticLevel::Warning => LogMessageLevel::Warning,
|
|
_ => LogMessageLevel::Debug,
|
|
};
|
|
|
|
log_messages::append_log_message(ui, level, location, d.message());
|
|
})
|
|
.fold(DiagnosticSummary::NothingDetected, |acc, d| {
|
|
match (acc, d.level()) {
|
|
(_, DiagnosticLevel::Error) => DiagnosticSummary::Errors,
|
|
(DiagnosticSummary::Errors, DiagnosticLevel::Warning) => DiagnosticSummary::Errors,
|
|
(_, DiagnosticLevel::Warning) => DiagnosticSummary::Warnings,
|
|
// DiagnosticLevel is non-exhaustive:
|
|
(acc, _) => acc,
|
|
}
|
|
});
|
|
|
|
let api = ui.global::<Api>();
|
|
api.set_diagnostic_summary(summary);
|
|
}
|
|
|
|
pub fn ui_set_known_components(
|
|
ui: &PreviewUi,
|
|
known_components: &[crate::common::ComponentInformation],
|
|
current_component_index: usize,
|
|
) {
|
|
let mut builtins_map: HashMap<String, Vec<ComponentItem>> = Default::default();
|
|
let mut std_widgets_map: HashMap<String, Vec<ComponentItem>> = Default::default();
|
|
let mut path_map: HashMap<PathBuf, (SharedString, Vec<ComponentItem>)> = Default::default();
|
|
let mut library_map: HashMap<String, Vec<ComponentItem>> = Default::default();
|
|
let mut longest_path_prefix = PathBuf::new();
|
|
|
|
for (idx, ci) in known_components.iter().enumerate() {
|
|
if ci.is_global {
|
|
continue;
|
|
}
|
|
let (url, pretty_location) = extract_definition_location(ci);
|
|
let item = ComponentItem {
|
|
name: ci.name.clone().into(),
|
|
index: idx.try_into().unwrap(),
|
|
defined_at: url.clone(),
|
|
pretty_location,
|
|
is_user_defined: !(ci.is_builtin || ci.is_std_widget),
|
|
is_currently_shown: idx == current_component_index,
|
|
is_exported: ci.is_exported,
|
|
};
|
|
|
|
if let Some(position) = &ci.defined_at {
|
|
if let Some(library) = position.url().path().strip_prefix("/@") {
|
|
library_map.entry(format!("@{library}")).or_default().push(item);
|
|
} else {
|
|
let path = i_slint_compiler::pathutils::clean_path(
|
|
&(position.url().to_file_path().unwrap_or_default()),
|
|
);
|
|
if path != PathBuf::new() {
|
|
if longest_path_prefix == PathBuf::new() {
|
|
longest_path_prefix = path.clone();
|
|
} else {
|
|
longest_path_prefix =
|
|
std::iter::zip(longest_path_prefix.components(), path.components())
|
|
.take_while(|(l, p)| l == p)
|
|
.map(|(l, _)| l)
|
|
.collect();
|
|
}
|
|
}
|
|
path_map.entry(path).or_insert((url, Vec::new())).1.push(item);
|
|
}
|
|
} else if ci.is_builtin {
|
|
builtins_map.entry(ci.category.clone()).or_default().push(item);
|
|
} else {
|
|
std_widgets_map.entry(ci.category.clone()).or_default().push(item);
|
|
}
|
|
}
|
|
|
|
type ComponentModel = search_model::SearchModel<ComponentItem>;
|
|
fn make_component_model(vec: Vec<ComponentItem>) -> ComponentModel {
|
|
ComponentModel::new(VecModel::from(vec), |i, search_str| {
|
|
search_model::contains(i.name.as_str(), search_str)
|
|
})
|
|
}
|
|
|
|
fn sort_subset(mut input: HashMap<String, Vec<ComponentItem>>) -> Vec<ComponentListItem> {
|
|
let mut output = input
|
|
.drain()
|
|
.map(|(k, mut v)| {
|
|
v.sort_by_key(|i| i.name.clone());
|
|
let model = Rc::new(make_component_model(v));
|
|
ComponentListItem {
|
|
category: k.into(),
|
|
file_url: SharedString::new(),
|
|
components: model.into(),
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
output.sort_by_key(|k| k.category.clone());
|
|
output
|
|
}
|
|
|
|
let builtin_components = sort_subset(builtins_map);
|
|
let std_widgets_components = sort_subset(std_widgets_map);
|
|
let library_components = sort_subset(library_map);
|
|
|
|
let mut file_components = path_map
|
|
.drain()
|
|
.map(|(p, (file_url, mut v))| {
|
|
v.sort_by_key(|i| i.name.clone());
|
|
let model = Rc::new(make_component_model(v));
|
|
let name = if p == longest_path_prefix {
|
|
p.file_name().unwrap_or_default().to_string_lossy().to_string()
|
|
} else {
|
|
p.strip_prefix(&longest_path_prefix).unwrap_or(&p).to_string_lossy().to_string()
|
|
};
|
|
ComponentListItem { category: name.into(), file_url, components: model.into() }
|
|
})
|
|
.collect::<Vec<_>>();
|
|
file_components.sort_by_key(|k| PathBuf::from(k.category.to_string()));
|
|
|
|
let mut all_components = Vec::with_capacity(
|
|
builtin_components.len() + library_components.len() + file_components.len(),
|
|
);
|
|
all_components.extend_from_slice(&builtin_components[..]);
|
|
all_components.extend_from_slice(&std_widgets_components[..]);
|
|
all_components.extend_from_slice(&library_components[..]);
|
|
all_components.extend_from_slice(&file_components[..]);
|
|
|
|
let result = Rc::new(search_model::SearchModel::new(
|
|
VecModel::from(all_components),
|
|
|category, search_str| {
|
|
let mut yes = search_str.is_empty();
|
|
if let Some(sub_filter) = category.components.as_any().downcast_ref::<ComponentModel>()
|
|
{
|
|
sub_filter.set_search_text(search_str.clone());
|
|
yes = yes || sub_filter.row_count() > 0;
|
|
}
|
|
yes
|
|
},
|
|
));
|
|
let api = ui.global::<Api>();
|
|
|
|
let old_search_text = api
|
|
.get_known_components()
|
|
.as_any()
|
|
.downcast_ref::<search_model::SearchModel<ComponentListItem>>()
|
|
.map(|x| x.search_text())
|
|
.filter(|x| !x.is_empty());
|
|
if let Some(search_text) = old_search_text {
|
|
result.set_search_text(search_text.clone());
|
|
}
|
|
|
|
api.set_known_components(result.clone().into());
|
|
api.on_library_search(move |term| {
|
|
result.set_search_text(term.into());
|
|
});
|
|
}
|
|
|
|
fn to_ui_range(r: TextRange) -> Option<Range> {
|
|
Some(Range {
|
|
start: i32::try_from(u32::from(r.start())).ok()?,
|
|
end: i32::try_from(u32::from(r.end())).ok()?,
|
|
})
|
|
}
|
|
|
|
fn convert_simple_string(input: SharedString) -> SharedString {
|
|
slint::format!("\"{}\"", str::escape_debug(input.as_ref()))
|
|
}
|
|
|
|
fn string_to_code(
|
|
input: SharedString,
|
|
is_translatable: bool,
|
|
tr_context: SharedString,
|
|
tr_plural: SharedString,
|
|
tr_plural_expression: SharedString,
|
|
) -> SharedString {
|
|
let input = convert_simple_string(input);
|
|
if !is_translatable {
|
|
input
|
|
} else {
|
|
let context = if tr_context.is_empty() {
|
|
SharedString::new()
|
|
} else {
|
|
slint::format!("{} => ", convert_simple_string(tr_context))
|
|
};
|
|
let plural = if tr_plural.is_empty() {
|
|
SharedString::new()
|
|
} else {
|
|
slint::format!(" | {} % {}", convert_simple_string(tr_plural), tr_plural_expression)
|
|
};
|
|
slint::format!("@tr({context}{input}{plural})")
|
|
}
|
|
}
|
|
|
|
fn unit_model(units: &[expression_tree::Unit]) -> ModelRc<SharedString> {
|
|
Rc::new(VecModel::from(
|
|
units.iter().map(|u| u.to_string().into()).collect::<Vec<SharedString>>(),
|
|
))
|
|
.into()
|
|
}
|
|
|
|
fn is_equal_value(c: &PropertyValue, n: &PropertyValue) -> bool {
|
|
c.code == n.code
|
|
}
|
|
|
|
fn is_equal_property(c: &PropertyInformation, n: &PropertyInformation) -> bool {
|
|
c.name == n.name && c.type_name == n.type_name && is_equal_value(&c.value, &n.value)
|
|
}
|
|
|
|
fn is_equal_element(c: &ElementInformation, n: &ElementInformation) -> bool {
|
|
c.id == n.id
|
|
&& c.type_name == n.type_name
|
|
&& c.source_uri == n.source_uri
|
|
&& c.offset == n.offset
|
|
}
|
|
|
|
pub type PropertyGroupModel = ModelRc<PropertyGroup>;
|
|
|
|
fn update_grouped_properties(
|
|
cvg: &VecModel<PropertyInformation>,
|
|
nvg: &VecModel<PropertyInformation>,
|
|
) {
|
|
enum Op {
|
|
Insert((usize, usize)),
|
|
Copy((usize, usize)),
|
|
PushBack(usize),
|
|
Remove(usize),
|
|
}
|
|
|
|
let mut to_do = Vec::new();
|
|
|
|
let mut c_it = cvg.iter();
|
|
let mut n_it = nvg.iter();
|
|
|
|
let mut cp = c_it.next();
|
|
let mut np = n_it.next();
|
|
|
|
let mut c_index = 0_usize;
|
|
let mut n_index = 0_usize;
|
|
|
|
loop {
|
|
match (cp.as_ref(), np.as_ref()) {
|
|
(None, None) => break,
|
|
(Some(_), None) => {
|
|
to_do.push(Op::Remove(c_index));
|
|
cp = c_it.next();
|
|
}
|
|
(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();
|
|
}
|
|
std::cmp::Ordering::Equal => {
|
|
if !is_equal_property(c, n) {
|
|
to_do.push(Op::Copy((c_index, n_index)));
|
|
}
|
|
c_index += 1;
|
|
n_index += 1;
|
|
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;
|
|
np = n_it.next();
|
|
}
|
|
}
|
|
}
|
|
|
|
for op in &to_do {
|
|
match op {
|
|
Op::Insert((c, n)) => {
|
|
cvg.insert(*c, nvg.row_data(*n).unwrap());
|
|
}
|
|
Op::Copy((c, n)) => {
|
|
cvg.set_row_data(*c, nvg.row_data(*n).unwrap());
|
|
}
|
|
Op::PushBack(n) => {
|
|
cvg.push(nvg.row_data(*n).unwrap());
|
|
}
|
|
Op::Remove(c) => {
|
|
cvg.remove(*c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_value<T: Sized + std::convert::TryFrom<slint_interpreter::Value> + std::default::Default>(
|
|
v: &Option<slint_interpreter::Value>,
|
|
) -> T {
|
|
v.clone().and_then(|v| v.try_into().ok()).unwrap_or_default()
|
|
}
|
|
|
|
fn get_code(v: &Option<slint_interpreter::Value>) -> SharedString {
|
|
v.as_ref()
|
|
.and_then(|v| slint_interpreter::json::value_to_json(v).ok())
|
|
.and_then(|j| serde_json::to_string_pretty(&j).ok())
|
|
.unwrap_or_default()
|
|
.into()
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
struct ValueMapping {
|
|
name_prefix: SharedString,
|
|
is_too_complex: bool,
|
|
is_array: bool,
|
|
headers: Vec<SharedString>,
|
|
current_values: Vec<PropertyValue>,
|
|
array_values: Vec<Vec<PropertyValue>>,
|
|
code_value: PropertyValue,
|
|
}
|
|
|
|
fn map_value_and_type(
|
|
ty: &langtype::Type,
|
|
value: &Option<slint_interpreter::Value>,
|
|
mapping: &mut ValueMapping,
|
|
) {
|
|
fn map_color(
|
|
mapping: &mut ValueMapping,
|
|
color: slint::Color,
|
|
kind: PropertyValueKind,
|
|
code: SharedString,
|
|
) {
|
|
let color_string = brushes::color_to_string(color);
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
value_kind: kind,
|
|
kind,
|
|
display_string: color_string.clone(),
|
|
brush_kind: BrushKind::Solid,
|
|
value_brush: slint::Brush::SolidColor(color),
|
|
gradient_stops: Rc::new(VecModel::from(vec![GradientStop { color, position: 0.5 }]))
|
|
.into(),
|
|
code,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
use i_slint_compiler::expression_tree::Unit;
|
|
use langtype::Type;
|
|
|
|
match ty {
|
|
Type::Float32 => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: get_value::<f32>(value).to_shared_string(),
|
|
kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
code: get_code(value),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
Type::Int32 => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: get_value::<i32>(value).to_shared_string(),
|
|
kind: PropertyValueKind::Integer,
|
|
value_kind: PropertyValueKind::Integer,
|
|
value_int: get_value::<i32>(value),
|
|
code: get_code(value),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Duration => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Ms),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[Unit::S, Unit::Ms]),
|
|
value_int: 1,
|
|
code: get_code(value),
|
|
default_selection: 1,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::PhysicalLength => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Phx),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[
|
|
Unit::Px,
|
|
Unit::Cm,
|
|
Unit::Mm,
|
|
Unit::In,
|
|
Unit::Pt,
|
|
Unit::Phx,
|
|
Unit::Rem,
|
|
]),
|
|
value_int: 5,
|
|
code: get_code(value),
|
|
default_selection: 5,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::LogicalLength => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Px),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[
|
|
Unit::Px,
|
|
Unit::Cm,
|
|
Unit::Mm,
|
|
Unit::In,
|
|
Unit::Pt,
|
|
Unit::Phx,
|
|
Unit::Rem,
|
|
]),
|
|
value_int: 0,
|
|
code: get_code(value),
|
|
default_selection: 0,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Rem => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Rem),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[
|
|
Unit::Px,
|
|
Unit::Cm,
|
|
Unit::Mm,
|
|
Unit::In,
|
|
Unit::Pt,
|
|
Unit::Phx,
|
|
Unit::Rem,
|
|
]),
|
|
value_int: 6,
|
|
code: get_code(value),
|
|
default_selection: 6,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Angle => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Deg),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[Unit::Deg, Unit::Grad, Unit::Turn, Unit::Rad]),
|
|
value_int: 0,
|
|
code: get_code(value),
|
|
default_selection: 0,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Percent => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("{}{}", get_value::<f32>(value), Unit::Percent),
|
|
kind: PropertyValueKind::Float,
|
|
value_kind: PropertyValueKind::Float,
|
|
value_float: get_value::<f32>(value),
|
|
visual_items: unit_model(&[Unit::Percent]),
|
|
value_int: 0,
|
|
code: get_code(value),
|
|
default_selection: 0,
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::String => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!("\"{}\"", get_value::<SharedString>(value)),
|
|
kind: PropertyValueKind::String,
|
|
value_kind: PropertyValueKind::String,
|
|
value_string: get_value::<SharedString>(value),
|
|
code: get_code(value),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Color => {
|
|
map_color(
|
|
mapping,
|
|
get_value::<slint::Color>(value),
|
|
PropertyValueKind::Color,
|
|
get_code(value),
|
|
);
|
|
}
|
|
Type::Brush => {
|
|
let brush = get_value::<slint::Brush>(value);
|
|
match brush {
|
|
slint::Brush::SolidColor(c) => {
|
|
map_color(mapping, c, PropertyValueKind::Brush, get_code(value))
|
|
}
|
|
slint::Brush::LinearGradient(lg) => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: SharedString::from("Linear Gradient"),
|
|
kind: PropertyValueKind::Brush,
|
|
value_kind: PropertyValueKind::Brush,
|
|
brush_kind: BrushKind::Linear,
|
|
value_float: lg.angle(),
|
|
value_brush: slint::Brush::LinearGradient(lg.clone()),
|
|
gradient_stops: Rc::new(VecModel::from(
|
|
lg.stops()
|
|
.map(|gs| GradientStop { color: gs.color, position: gs.position })
|
|
.collect::<Vec<_>>(),
|
|
))
|
|
.into(),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
slint::Brush::RadialGradient(rg) => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: SharedString::from("Radial Gradient"),
|
|
kind: PropertyValueKind::Brush,
|
|
value_kind: PropertyValueKind::Brush,
|
|
brush_kind: BrushKind::Radial,
|
|
value_brush: slint::Brush::RadialGradient(rg.clone()),
|
|
gradient_stops: Rc::new(VecModel::from(
|
|
rg.stops()
|
|
.map(|gs| GradientStop { color: gs.color, position: gs.position })
|
|
.collect::<Vec<_>>(),
|
|
))
|
|
.into(),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
slint::Brush::ConicGradient(cg) => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: SharedString::from("Conic Gradient"),
|
|
kind: PropertyValueKind::Brush,
|
|
value_kind: PropertyValueKind::Brush,
|
|
brush_kind: BrushKind::Conic,
|
|
value_brush: slint::Brush::ConicGradient(cg.clone()),
|
|
gradient_stops: Rc::new(VecModel::from(
|
|
cg.stops()
|
|
.map(|gs| GradientStop { color: gs.color, position: gs.position })
|
|
.collect::<Vec<_>>(),
|
|
))
|
|
.into(),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
_ => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: SharedString::from("Unknown Brush"),
|
|
kind: PropertyValueKind::Code,
|
|
value_kind: PropertyValueKind::Code,
|
|
value_string: SharedString::from("???"),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Type::Bool => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: get_value::<bool>(value).to_shared_string(),
|
|
kind: PropertyValueKind::Boolean,
|
|
value_kind: PropertyValueKind::Boolean,
|
|
value_bool: get_value::<bool>(value),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Enumeration(enumeration) => {
|
|
let selected_value = match &value {
|
|
Some(slint_interpreter::Value::EnumerationValue(_, k)) => enumeration
|
|
.values
|
|
.iter()
|
|
.position(|v| v.as_str() == k)
|
|
.unwrap_or(enumeration.default_value),
|
|
_ => enumeration.default_value,
|
|
};
|
|
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: slint::format!(
|
|
"{}.{}",
|
|
enumeration.name,
|
|
enumeration.values[selected_value]
|
|
),
|
|
kind: PropertyValueKind::Enum,
|
|
value_kind: PropertyValueKind::Enum,
|
|
value_string: enumeration.name.as_str().into(),
|
|
default_selection: i32::try_from(enumeration.default_value).unwrap_or_default(),
|
|
value_int: i32::try_from(selected_value).unwrap_or_default(),
|
|
visual_items: Rc::new(VecModel::from(
|
|
enumeration
|
|
.values
|
|
.iter()
|
|
.map(|s| SharedString::from(s.as_str()))
|
|
.collect::<Vec<_>>(),
|
|
))
|
|
.into(),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
Type::Array(array_ty) => {
|
|
mapping.is_array = true;
|
|
let model = get_value::<ModelRc<slint_interpreter::Value>>(value);
|
|
|
|
for (idx, sub_value) in model.iter().enumerate() {
|
|
let mut sub_mapping =
|
|
ValueMapping { name_prefix: mapping.name_prefix.clone(), ..Default::default() };
|
|
map_value_and_type(array_ty, &Some(sub_value), &mut sub_mapping);
|
|
|
|
let sub_mapping_too_complex = sub_mapping.is_array || sub_mapping.is_too_complex;
|
|
mapping.is_too_complex = mapping.is_too_complex || sub_mapping_too_complex;
|
|
|
|
if sub_mapping_too_complex {
|
|
if idx == 0 {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
}
|
|
mapping.array_values.push(vec![std::mem::take(&mut sub_mapping.code_value)]);
|
|
} else {
|
|
if idx == 0 {
|
|
mapping.headers.extend_from_slice(&sub_mapping.headers);
|
|
}
|
|
mapping.array_values.push(std::mem::take(&mut sub_mapping.array_values[0]));
|
|
}
|
|
}
|
|
}
|
|
Type::Struct(s) => {
|
|
mapping.is_array = false;
|
|
|
|
let struct_data = get_value::<slint_interpreter::Struct>(value);
|
|
|
|
for (field, field_ty) in s.fields.iter() {
|
|
let field = field.to_string();
|
|
let mut sub_mapping = ValueMapping::default();
|
|
let header_name = if mapping.name_prefix.is_empty() {
|
|
field.clone().into()
|
|
} else {
|
|
slint::format!("{}.{field}", mapping.name_prefix)
|
|
};
|
|
sub_mapping.name_prefix = header_name.clone();
|
|
|
|
map_value_and_type(
|
|
field_ty,
|
|
&struct_data.get_field(&field).cloned(),
|
|
&mut sub_mapping,
|
|
);
|
|
|
|
let sub_mapping_too_complex = sub_mapping.is_array || sub_mapping.is_too_complex;
|
|
|
|
mapping.is_too_complex = mapping.is_too_complex || sub_mapping_too_complex;
|
|
|
|
if sub_mapping_too_complex {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(std::mem::take(&mut sub_mapping.code_value));
|
|
} else {
|
|
mapping.headers.extend_from_slice(&sub_mapping.headers);
|
|
mapping.current_values.extend_from_slice(&sub_mapping.array_values[0]);
|
|
}
|
|
}
|
|
}
|
|
Type::Image | Type::Model | Type::PathData | Type::Easing | Type::UnitProduct(_) => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.is_too_complex = true;
|
|
}
|
|
_ => {
|
|
mapping.headers.push(mapping.name_prefix.clone());
|
|
mapping.current_values.push(PropertyValue {
|
|
display_string: "Unsupported type".into(),
|
|
kind: PropertyValueKind::Code,
|
|
value_kind: PropertyValueKind::Code,
|
|
value_string: "???".into(),
|
|
accessor_path: mapping.name_prefix.clone(),
|
|
code: get_code(value),
|
|
..Default::default()
|
|
});
|
|
}
|
|
}
|
|
|
|
if mapping.array_values.is_empty() {
|
|
mapping.array_values = vec![std::mem::take(&mut mapping.current_values)];
|
|
}
|
|
|
|
mapping.code_value = PropertyValue {
|
|
kind: PropertyValueKind::Code,
|
|
value_kind: PropertyValueKind::Code,
|
|
code: get_code(value),
|
|
..Default::default()
|
|
};
|
|
}
|
|
|
|
pub fn map_value_and_type_to_property_value(
|
|
ty: &langtype::Type,
|
|
value: &Option<slint_interpreter::Value>,
|
|
name_prefix: &str,
|
|
) -> PropertyValue {
|
|
let mut mapping =
|
|
ValueMapping { name_prefix: SharedString::from(name_prefix), ..Default::default() };
|
|
|
|
map_value_and_type(ty, value, &mut mapping);
|
|
|
|
if mapping.is_too_complex
|
|
|| mapping.array_values.len() != 1
|
|
|| mapping.array_values[0].len() != 1
|
|
{
|
|
mapping.code_value
|
|
} else {
|
|
mapping
|
|
.array_values
|
|
.first()
|
|
.and_then(|av| av.first())
|
|
.cloned()
|
|
.unwrap_or_else(|| mapping.code_value.clone())
|
|
}
|
|
}
|
|
|
|
fn map_preview_data_to_property_value(preview_data: &preview_data::PreviewData) -> PropertyValue {
|
|
map_value_and_type_to_property_value(&preview_data.ty, &preview_data.value, "")
|
|
}
|
|
|
|
fn map_preview_data_property(
|
|
key: &preview_data::PreviewDataKey,
|
|
value: &preview_data::PreviewData,
|
|
) -> Option<PreviewData> {
|
|
if !value.is_property() {
|
|
return None;
|
|
};
|
|
|
|
let has_getter = value.has_getter();
|
|
let has_setter = value.has_setter();
|
|
|
|
let mut mapping = ValueMapping::default();
|
|
map_value_and_type(&value.ty, &value.value, &mut mapping);
|
|
|
|
let is_array = mapping.array_values.len() != 1 || mapping.array_values[0].len() != 1;
|
|
let is_too_complex = mapping.is_too_complex;
|
|
|
|
Some(PreviewData {
|
|
name: SharedString::from(&key.property_name),
|
|
has_getter,
|
|
has_setter,
|
|
kind: match (is_array, is_too_complex) {
|
|
(false, false) => PreviewDataKind::Value,
|
|
(true, false) => PreviewDataKind::Table,
|
|
_ => PreviewDataKind::Json,
|
|
},
|
|
})
|
|
}
|
|
|
|
pub fn ui_set_preview_data(
|
|
ui: &PreviewUi,
|
|
preview_data: preview_data::PreviewDataMap,
|
|
previewed_component: Option<String>,
|
|
) {
|
|
fn create_container(
|
|
container_name: String,
|
|
it: &mut dyn Iterator<Item = (&preview_data::PreviewDataKey, &preview_data::PreviewData)>,
|
|
) -> Option<PropertyContainer> {
|
|
let (id, props) = it.filter_map(|(k, v)| Some((k, map_preview_data_property(k, v)?))).fold(
|
|
(None, vec![]),
|
|
move |mut acc, (key, value)| {
|
|
acc.0 = Some(acc.0.unwrap_or_else(|| key.container.clone()));
|
|
acc.1.push(value);
|
|
acc
|
|
},
|
|
);
|
|
Some(PropertyContainer {
|
|
container_name: container_name.into(),
|
|
container_id: id?.to_string().into(),
|
|
properties: Rc::new(VecModel::from(props)).into(),
|
|
})
|
|
}
|
|
|
|
let mut result: Vec<PropertyContainer> = vec![];
|
|
|
|
if let Some(c) = create_container(
|
|
previewed_component.unwrap_or_else(|| "<MAIN>".to_string()),
|
|
&mut preview_data
|
|
.iter()
|
|
.filter(|(k, _)| k.container == preview_data::PropertyContainer::Main),
|
|
) {
|
|
result.push(c);
|
|
}
|
|
|
|
for (k, mut chunk) in &preview_data
|
|
.iter()
|
|
.filter(|(k, _)| k.container != preview_data::PropertyContainer::Main)
|
|
.chunk_by(|(k, _)| k.container.clone())
|
|
{
|
|
if let Some(c) = create_container(k.to_string(), &mut chunk) {
|
|
result.push(c);
|
|
}
|
|
}
|
|
|
|
let api = ui.global::<Api>();
|
|
|
|
api.set_preview_data(Rc::new(VecModel::from(result)).into());
|
|
}
|
|
|
|
fn to_property_container(container: SharedString) -> preview_data::PropertyContainer {
|
|
if container.is_empty() || container == "<MAIN>" {
|
|
preview_data::PropertyContainer::Main
|
|
} else {
|
|
preview_data::PropertyContainer::Global(container.to_string())
|
|
}
|
|
}
|
|
|
|
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.as_str(),
|
|
)
|
|
})
|
|
.map(|pd| map_preview_data_to_property_value(&pd))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn map_preview_data_to_property_value_table(
|
|
preview_data: &preview_data::PreviewData,
|
|
) -> (bool, Vec<SharedString>, Vec<Vec<PropertyValue>>) {
|
|
let mut mapping = ValueMapping::default();
|
|
map_value_and_type(&preview_data.ty, &preview_data.value, &mut mapping);
|
|
|
|
let is_array = mapping.is_array;
|
|
let headers = std::mem::take(&mut mapping.headers);
|
|
let values = std::mem::take(&mut mapping.array_values);
|
|
|
|
(is_array, headers, values)
|
|
}
|
|
|
|
fn get_property_value_table(
|
|
container: SharedString,
|
|
property_name: SharedString,
|
|
) -> PropertyValueTable {
|
|
let (is_array, headers, mut values) = preview::component_instance()
|
|
.and_then(|component_instance| {
|
|
preview_data::get_preview_data(
|
|
&component_instance,
|
|
&to_property_container(container),
|
|
property_name.as_str(),
|
|
)
|
|
})
|
|
.map(|pd| map_preview_data_to_property_value_table(&pd))
|
|
.unwrap_or_else(|| (false, Default::default(), Default::default()));
|
|
|
|
let headers = Rc::new(VecModel::from(headers)).into();
|
|
let values = Rc::new(VecModel::from(
|
|
values.drain(..).map(|cv| Rc::new(VecModel::from(cv)).into()).collect::<Vec<_>>(),
|
|
))
|
|
.into();
|
|
|
|
PropertyValueTable { is_array, headers, values }
|
|
}
|
|
|
|
fn table_to_array(table: ModelRc<ModelRc<PropertyValue>>) -> Option<String> {
|
|
let mut result = "[\n".to_string();
|
|
|
|
for (row_number, row) in table.iter().enumerate() {
|
|
if row_number != 0 {
|
|
result += ",\n";
|
|
}
|
|
|
|
result += &table_row_to_struct(row, 1)?;
|
|
}
|
|
|
|
result += "\n]";
|
|
|
|
Some(result)
|
|
}
|
|
|
|
fn table_row_to_struct(row: ModelRc<PropertyValue>, indent_level: usize) -> Option<String> {
|
|
enum NodeKind {
|
|
Leaf(String),
|
|
Inner(BTreeMap<String, NodeKind>),
|
|
}
|
|
|
|
if row.row_count() == 1 {
|
|
if let Some(v) = row.row_data(0) {
|
|
if v.accessor_path.is_empty() {
|
|
// bare value!
|
|
return Some(format!("{}{}", " ".repeat(indent_level), v.code));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn structurize(row: ModelRc<PropertyValue>) -> Option<BTreeMap<String, NodeKind>> {
|
|
let mut result = BTreeMap::default();
|
|
|
|
fn insert(
|
|
map: &mut BTreeMap<String, NodeKind>,
|
|
accessor_path: &[&str],
|
|
value: String,
|
|
) -> Option<()> {
|
|
match accessor_path.len() {
|
|
0 => None,
|
|
1 => {
|
|
let prev = map
|
|
.insert(accessor_path.first().unwrap().to_string(), NodeKind::Leaf(value));
|
|
prev.is_none().then_some(())
|
|
}
|
|
_ => {
|
|
let n = map
|
|
.entry(accessor_path.first().unwrap().to_string())
|
|
.or_insert_with(|| NodeKind::Inner(BTreeMap::default()));
|
|
|
|
match n {
|
|
NodeKind::Leaf(_) => None,
|
|
NodeKind::Inner(m) => insert(m, &accessor_path[1..], value),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for col in row.iter() {
|
|
let ap = col.accessor_path.split('.').collect::<Vec<_>>();
|
|
let value = if col.was_edited { col.edited_value.clone() } else { col.code.clone() };
|
|
|
|
insert(&mut result, &ap[..], value.to_string())?;
|
|
}
|
|
|
|
Some(result)
|
|
}
|
|
|
|
let structure = structurize(row)?;
|
|
|
|
fn structure_to_string(
|
|
structure: &BTreeMap<String, NodeKind>,
|
|
indent_level: usize,
|
|
prefix: &str,
|
|
) -> Option<String> {
|
|
let indent_step = " ";
|
|
let mut result = format!("{}{prefix}{{\n", indent_step.repeat(indent_level));
|
|
|
|
let last_index = structure.len() - 1;
|
|
|
|
for (index, (k, v)) in structure.iter().enumerate() {
|
|
let comma = if index == last_index { "" } else { "," };
|
|
match v {
|
|
NodeKind::Leaf(v) => {
|
|
result +=
|
|
&format!("{}\"{k}\": {v}{comma}\n", indent_step.repeat(indent_level + 1))
|
|
}
|
|
NodeKind::Inner(m) => {
|
|
result += &structure_to_string(m, indent_level + 1, &format!("\"{k}\": "))?;
|
|
result += &format!("{comma}\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
result += &format!("{}}}", indent_step.repeat(indent_level));
|
|
|
|
Some(result)
|
|
}
|
|
|
|
structure_to_string(&structure, indent_level, "")
|
|
}
|
|
|
|
fn set_property_value_table(
|
|
container: SharedString,
|
|
property_name: SharedString,
|
|
table: ModelRc<ModelRc<PropertyValue>>,
|
|
is_array: bool,
|
|
) -> SharedString {
|
|
let json_string = if is_array {
|
|
table_to_array(table)
|
|
} else {
|
|
if table.row_count() != 1 {
|
|
// A struct must have exactly one row!
|
|
return "Malformed table".into();
|
|
}
|
|
|
|
table_row_to_struct(table.row_data(0).unwrap(), 0)
|
|
};
|
|
|
|
let Some(json_string) = json_string else {
|
|
return "Could not process input values".into();
|
|
};
|
|
|
|
set_json_preview_data(container, property_name, json_string.into())
|
|
}
|
|
|
|
fn default_property_value(source: &PropertyValue) -> PropertyValue {
|
|
let mut pv = PropertyValue {
|
|
kind: source.kind,
|
|
accessor_path: source.accessor_path.clone(),
|
|
..Default::default()
|
|
};
|
|
match source.kind {
|
|
PropertyValueKind::Boolean => {
|
|
pv.display_string = "false".into();
|
|
pv.code = "false".into();
|
|
}
|
|
PropertyValueKind::Brush => {
|
|
pv.display_string = "#00000000".into();
|
|
pv.brush_kind = BrushKind::Solid;
|
|
pv.value_brush = slint::Color::default().into();
|
|
pv.code = "#00000000".into();
|
|
}
|
|
PropertyValueKind::Code => {
|
|
pv.display_string = "Code".into();
|
|
}
|
|
PropertyValueKind::Color => {
|
|
pv.display_string = "#00000000".into();
|
|
pv.brush_kind = BrushKind::Solid;
|
|
pv.value_brush = slint::Color::default().into();
|
|
pv.code = "#00000000".into();
|
|
}
|
|
PropertyValueKind::Enum => {
|
|
let enum_selection: SharedString = format!(
|
|
"{}.{}",
|
|
source.value_string,
|
|
source
|
|
.visual_items
|
|
.row_data(source.default_selection.try_into().unwrap_or_default())
|
|
.unwrap_or_default(),
|
|
)
|
|
.into();
|
|
|
|
pv.display_string = enum_selection.clone();
|
|
pv.value_int = source.default_selection;
|
|
pv.value_string = source.value_string.clone();
|
|
pv.default_selection = source.default_selection;
|
|
pv.visual_items = source.visual_items.clone();
|
|
pv.code = enum_selection;
|
|
}
|
|
PropertyValueKind::Float => {
|
|
pv.display_string = "0.0".into();
|
|
pv.code = "0.0".into();
|
|
}
|
|
PropertyValueKind::Integer => {
|
|
pv.display_string = "0".into();
|
|
pv.code = "0".into();
|
|
}
|
|
PropertyValueKind::String => {
|
|
pv.display_string = "".into();
|
|
pv.code = "\"\"".into();
|
|
}
|
|
}
|
|
|
|
pv
|
|
}
|
|
|
|
fn insert_row_into_value_table(table: PropertyValueTable, insert_before: i32) {
|
|
if !table.is_array {
|
|
return;
|
|
}
|
|
|
|
let model = table.values.clone();
|
|
let insert_before = (insert_before as usize).clamp(0, model.row_count());
|
|
|
|
let Some(vec_model) = model.as_any().downcast_ref::<VecModel<ModelRc<PropertyValue>>>() else {
|
|
return;
|
|
};
|
|
|
|
let row_data = {
|
|
let mut result = vec![];
|
|
if let Some(row) = vec_model.row_data(0) {
|
|
result = row.iter().map(|pv| default_property_value(&pv)).collect::<Vec<_>>();
|
|
}
|
|
result
|
|
};
|
|
|
|
let row_model = Rc::new(VecModel::from(row_data));
|
|
if vec_model.row_count() == insert_before {
|
|
vec_model.push(row_model.into());
|
|
} else {
|
|
vec_model.insert(insert_before, row_model.into());
|
|
}
|
|
}
|
|
|
|
fn remove_row_from_value_table(table: PropertyValueTable, to_remove: i32) {
|
|
if to_remove < 0 || !table.is_array {
|
|
return;
|
|
}
|
|
let to_remove = to_remove as usize;
|
|
|
|
let model = table.values.clone();
|
|
let Some(vec_model) = model.as_any().downcast_ref::<VecModel<ModelRc<PropertyValue>>>() else {
|
|
return;
|
|
};
|
|
|
|
if to_remove < vec_model.row_count() {
|
|
vec_model.remove(to_remove);
|
|
}
|
|
}
|
|
|
|
fn set_json_preview_data(
|
|
container: SharedString,
|
|
property_name: SharedString,
|
|
json_string: SharedString,
|
|
) -> SharedString {
|
|
let property_name = (!property_name.is_empty()).then_some(property_name.to_string());
|
|
|
|
let json = match serde_json::from_str::<serde_json::Value>(json_string.as_ref()) {
|
|
Ok(j) => j,
|
|
Err(e) => {
|
|
return SharedString::from(format!("Input is not valid JSON: {e}"));
|
|
}
|
|
};
|
|
|
|
if property_name.is_none() && !json.is_object() {
|
|
return SharedString::from("Input for Slint Element is not a JSON object");
|
|
}
|
|
|
|
if let Some(ci) = preview::component_instance() {
|
|
match preview_data::set_json_preview_data(
|
|
&ci,
|
|
to_property_container(container),
|
|
property_name,
|
|
json,
|
|
) {
|
|
Ok(_) => SharedString::new(),
|
|
Err(v) => v.first().cloned().unwrap_or_default().into(),
|
|
}
|
|
} else {
|
|
SharedString::from("No preview loaded")
|
|
}
|
|
}
|
|
|
|
fn update_properties(
|
|
current_model: PropertyGroupModel,
|
|
next_model: PropertyGroupModel,
|
|
) -> PropertyGroupModel {
|
|
if current_model.row_count() != next_model.row_count() {
|
|
return next_model;
|
|
}
|
|
|
|
for (c, n) in std::iter::zip(current_model.iter(), next_model.iter()) {
|
|
debug_assert_eq!(c.group_name, n.group_name);
|
|
|
|
fn extract_inner_model<'a>(m: &'a PropertyGroup) -> &'a VecModel<PropertyInformation> {
|
|
m.properties
|
|
.as_any()
|
|
.downcast_ref::<search_model::SearchModel<PropertyInformation>>()
|
|
.unwrap()
|
|
.source_model()
|
|
.as_any()
|
|
.downcast_ref::<VecModel<PropertyInformation>>()
|
|
.unwrap()
|
|
}
|
|
|
|
let cvg = extract_inner_model(&c);
|
|
let nvg = extract_inner_model(&n);
|
|
|
|
update_grouped_properties(cvg, nvg);
|
|
}
|
|
|
|
current_model
|
|
}
|
|
|
|
pub fn ui_set_properties(
|
|
ui: &PreviewUi,
|
|
document_cache: &common::DocumentCache,
|
|
properties: Option<properties::QueryPropertyResponse>,
|
|
) -> PropertyDeclarations {
|
|
let win = i_slint_core::window::WindowInner::from_pub(ui.window()).window_adapter();
|
|
let Some((next_element, declarations, next_model)) =
|
|
property_view::map_properties_to_ui(document_cache, properties, &win)
|
|
else {
|
|
let api = ui.global::<Api>();
|
|
api.set_properties(ModelRc::default());
|
|
api.set_current_element(ElementInformation::default());
|
|
return Default::default();
|
|
};
|
|
|
|
let api = ui.global::<Api>();
|
|
let current_model = api.get_properties();
|
|
|
|
let element = api.get_current_element();
|
|
if !is_equal_element(&element, &next_element) {
|
|
let old_search_text = current_model
|
|
.as_any()
|
|
.downcast_ref::<search_model::SearchModel<PropertyGroup>>()
|
|
.map(|x| x.search_text())
|
|
.filter(|x| !x.is_empty());
|
|
if let Some(search_text) = old_search_text {
|
|
next_model.set_search_text(search_text.clone());
|
|
}
|
|
|
|
api.set_properties(next_model.clone().into());
|
|
api.on_properties_search(move |search_text| {
|
|
next_model.set_search_text(search_text);
|
|
});
|
|
} else {
|
|
update_properties(current_model, next_model.into());
|
|
}
|
|
api.set_current_element(next_element);
|
|
|
|
declarations
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::preview::preview_data;
|
|
|
|
use slint::{Model, SharedString, ToSharedString, VecModel};
|
|
|
|
use super::{PropertyInformation, PropertyValue, PropertyValueKind};
|
|
|
|
fn create_test_property(name: &str, value: &str) -> PropertyInformation {
|
|
PropertyInformation {
|
|
name: name.into(),
|
|
display_priority: 1000,
|
|
type_name: "Sometype".into(),
|
|
value: PropertyValue {
|
|
kind: PropertyValueKind::String,
|
|
value_string: value.into(),
|
|
code: value.into(),
|
|
..Default::default()
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_property_date_update() {
|
|
let current = VecModel::from(vec![
|
|
create_test_property("aaa", "AAA"),
|
|
create_test_property("bbb", "BBB"),
|
|
create_test_property("ccc", "CCC"),
|
|
create_test_property("ddd", "DDD"),
|
|
create_test_property("eee", "EEE"),
|
|
]);
|
|
let next = VecModel::from(vec![
|
|
create_test_property("aaa", "AAA"),
|
|
create_test_property("aab", "AAB"),
|
|
create_test_property("abb", "ABB"),
|
|
create_test_property("bbb", "BBBX"),
|
|
create_test_property("ddd", "DDD"),
|
|
]);
|
|
|
|
super::update_grouped_properties(¤t, &next);
|
|
|
|
let mut it = current.iter();
|
|
|
|
let t = it.next().unwrap();
|
|
assert_eq!(t.name.as_str(), "aaa");
|
|
assert_eq!(t.value.code.as_str(), "AAA");
|
|
|
|
let t = it.next().unwrap();
|
|
assert_eq!(t.name.as_str(), "aab");
|
|
assert_eq!(t.value.code.as_str(), "AAB");
|
|
|
|
let t = it.next().unwrap();
|
|
assert_eq!(t.name.as_str(), "abb");
|
|
assert_eq!(t.value.code.as_str(), "ABB");
|
|
|
|
let t = it.next().unwrap();
|
|
assert_eq!(t.name.as_str(), "bbb");
|
|
assert_eq!(t.value.code.as_str(), "BBBX");
|
|
|
|
let t = it.next().unwrap();
|
|
assert_eq!(t.name.as_str(), "ddd");
|
|
assert_eq!(t.value.code.as_str(), "DDD");
|
|
|
|
assert!(it.next().is_none());
|
|
}
|
|
|
|
fn generate_preview_data(
|
|
visibility: &str,
|
|
type_def: &str,
|
|
type_name: &str,
|
|
code: &str,
|
|
) -> (crate::preview::preview_data::PreviewDataKey, crate::preview::preview_data::PreviewData)
|
|
{
|
|
let component_instance = crate::preview::test::interpret_test(
|
|
"fluent",
|
|
&format!(
|
|
r#"
|
|
{type_def}
|
|
export component Tester {{
|
|
{visibility} property <{type_name}> test: {code};
|
|
}}
|
|
"#
|
|
),
|
|
);
|
|
let mut data =
|
|
preview_data::query_preview_data_properties_and_callbacks(&component_instance);
|
|
assert_eq!(data.len(), 1);
|
|
data.pop_first().unwrap()
|
|
}
|
|
|
|
fn compare_pv(r: &super::PropertyValue, e: &PropertyValue) {
|
|
eprintln!("Received: {r:?}");
|
|
eprintln!("Expected: {e:?}");
|
|
|
|
assert_eq!(r.value_bool, e.value_bool);
|
|
assert_eq!(r.is_translatable, e.is_translatable);
|
|
assert_eq!(r.value_brush, e.value_brush);
|
|
assert_eq!(r.value_float, e.value_float);
|
|
assert_eq!(r.value_int, e.value_int);
|
|
assert_eq!(r.default_selection, e.default_selection);
|
|
assert_eq!(r.value_string, e.value_string);
|
|
assert_eq!(r.tr_context, e.tr_context);
|
|
assert_eq!(r.tr_plural, e.tr_plural);
|
|
assert_eq!(r.tr_plural_expression, e.tr_plural_expression);
|
|
assert_eq!(r.code, e.code);
|
|
|
|
assert_eq!(r.visual_items.row_count(), e.visual_items.row_count());
|
|
for (r, e) in r.visual_items.iter().zip(e.visual_items.iter()) {
|
|
assert_eq!(r, e);
|
|
}
|
|
}
|
|
|
|
fn validate_rp_impl(
|
|
visibility: &str,
|
|
type_def: &str,
|
|
type_name: &str,
|
|
code: &str,
|
|
expected_data: super::PreviewData,
|
|
) -> (preview_data::PreviewDataKey, preview_data::PreviewData) {
|
|
let (key, value) = generate_preview_data(visibility, type_def, type_name, code);
|
|
let rp = super::map_preview_data_property(&key, &value).unwrap();
|
|
|
|
eprintln!("*** Validating PreviewData: Received: {rp:?}");
|
|
eprintln!("*** Validating PreviewData: Expected: {expected_data:?}");
|
|
|
|
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!("*** PreviewData is as expected...");
|
|
|
|
(key, value)
|
|
}
|
|
|
|
fn validate_rp(
|
|
visibility: &str,
|
|
type_def: &str,
|
|
type_name: &str,
|
|
code: &str,
|
|
expected_data: super::PreviewData,
|
|
expected_value: super::PropertyValue,
|
|
) {
|
|
let (_, value) = validate_rp_impl(visibility, type_def, type_name, code, expected_data);
|
|
|
|
let pv = super::map_preview_data_to_property_value(&value);
|
|
compare_pv(&pv, &expected_value);
|
|
|
|
let (is_array, headers, values) = super::map_preview_data_to_property_value_table(&value);
|
|
assert!(!is_array);
|
|
assert!(headers.len() == 1);
|
|
assert!(headers[0].is_empty());
|
|
assert_eq!(values.len(), 1);
|
|
assert_eq!(values.first().unwrap().len(), 1);
|
|
}
|
|
|
|
fn validate_table_rp(
|
|
visibility: &str,
|
|
type_def: &str,
|
|
type_name: &str,
|
|
code: &str,
|
|
expected_data: super::PreviewData,
|
|
expected_code: &str,
|
|
expected_is_array: bool,
|
|
expected_headers: Vec<String>,
|
|
expected_table: Vec<Vec<super::PropertyValue>>,
|
|
) {
|
|
let (_, value) = validate_rp_impl(visibility, type_def, type_name, code, expected_data);
|
|
|
|
let pv = super::map_preview_data_to_property_value(&value);
|
|
compare_pv(
|
|
&pv,
|
|
&super::PropertyValue {
|
|
kind: super::PropertyValueKind::Code,
|
|
code: expected_code.into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
|
|
let (is_array, headers, values) = super::map_preview_data_to_property_value_table(&value);
|
|
|
|
assert_eq!(is_array, expected_is_array);
|
|
|
|
for (idx, h) in headers.iter().enumerate() {
|
|
eprintln!("Header {idx}: \"{h}\"");
|
|
}
|
|
assert_eq!(headers.len(), expected_headers.len());
|
|
assert!(headers.iter().zip(expected_headers.iter()).all(|(rh, eh)| rh == eh));
|
|
|
|
assert_eq!(values.len(), expected_table.len());
|
|
for (rr, er) in values.iter().zip(expected_table.iter()) {
|
|
assert!(!rr.is_empty());
|
|
assert_eq!(rr.len(), er.len());
|
|
rr.iter().zip(er.iter()).for_each(|(rv, ev)| compare_pv(rv, ev));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_string() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"string",
|
|
"\"Test\"",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "\"Test\"".into(),
|
|
code: "\"Test\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "Test".into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_length_px() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"length",
|
|
"100px",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "100".into(),
|
|
code: "100".into(),
|
|
kind: super::PropertyValueKind::Float,
|
|
value_float: 100.0,
|
|
visual_items: std::rc::Rc::new(VecModel::from(vec![
|
|
"px".into(),
|
|
"cm".into(),
|
|
"mm".into(),
|
|
"in".into(),
|
|
"pt".into(),
|
|
"phx".into(),
|
|
"rem".into(),
|
|
]))
|
|
.into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_length_cm() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"length",
|
|
"10cm",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "378px".into(),
|
|
code: "378".into(),
|
|
kind: super::PropertyValueKind::Float,
|
|
value_float: 378.0,
|
|
visual_items: std::rc::Rc::new(VecModel::from(vec![
|
|
"px".into(),
|
|
"cm".into(),
|
|
"mm".into(),
|
|
"in".into(),
|
|
"pt".into(),
|
|
"phx".into(),
|
|
"rem".into(),
|
|
]))
|
|
.into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_duration() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"duration",
|
|
"100s",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "100000ms".into(),
|
|
code: "100000".into(),
|
|
kind: super::PropertyValueKind::Float,
|
|
value_float: 100000.0,
|
|
visual_items: std::rc::Rc::new(VecModel::from(vec!["s".into(), "ms".into()]))
|
|
.into(),
|
|
default_selection: 1,
|
|
value_int: 1,
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_angle() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"angle",
|
|
"100turn",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "36000deg".into(),
|
|
code: "36000".into(),
|
|
kind: super::PropertyValueKind::Float,
|
|
value_float: 36000.0,
|
|
visual_items: std::rc::Rc::new(VecModel::from(vec![
|
|
"deg".into(),
|
|
"grad".into(),
|
|
"turn".into(),
|
|
"rad".into(),
|
|
]))
|
|
.into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_percent() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"percent",
|
|
"10%",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "10%".into(),
|
|
code: "10".into(),
|
|
kind: super::PropertyValueKind::Float,
|
|
value_float: 10.0,
|
|
visual_items: std::rc::Rc::new(VecModel::from(vec!["%".into()])).into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_color() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"color",
|
|
"#aabbcc",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "#aabbcc".into(),
|
|
code: "\"#aabbcc\"".into(),
|
|
kind: super::PropertyValueKind::Color,
|
|
value_brush: slint::Brush::SolidColor(slint::Color::from_argb_u8(
|
|
0xff, 0xaa, 0xbb, 0xcc,
|
|
)),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_int() {
|
|
validate_rp(
|
|
"in",
|
|
"",
|
|
"int",
|
|
"12",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "12".into(),
|
|
code: "12".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 12,
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_bool_true() {
|
|
validate_rp(
|
|
"out",
|
|
"",
|
|
"bool",
|
|
"true",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "true".into(),
|
|
code: "true".into(),
|
|
kind: super::PropertyValueKind::Boolean,
|
|
value_bool: true,
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_bool_false() {
|
|
validate_rp(
|
|
"in-out",
|
|
"",
|
|
"bool",
|
|
"false",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Value,
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "false".into(),
|
|
code: "false".into(),
|
|
kind: super::PropertyValueKind::Boolean,
|
|
value_bool: false,
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_struct_with_array() {
|
|
validate_rp(
|
|
"in-out",
|
|
r#"
|
|
struct FooStruct { first: [string] }
|
|
"#,
|
|
"FooStruct",
|
|
"{ first: [ \"first of a kind\", \"second of a kind\"] }",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Json,
|
|
},
|
|
super::PropertyValue {
|
|
kind: super::PropertyValueKind::Code,
|
|
code:
|
|
"{\n \"first\": [\n \"first of a kind\",\n \"second of a kind\"\n ]\n}"
|
|
.into(),
|
|
..Default::default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_struct() {
|
|
validate_table_rp(
|
|
"in-out",
|
|
"struct FooStruct { bar: bool, count: int }",
|
|
"FooStruct",
|
|
"{ bar: true, count: 23 }",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Table,
|
|
},
|
|
"{\n \"bar\": true,\n \"count\": 23\n}",
|
|
false,
|
|
vec!["bar".into(), "count".into()],
|
|
vec![vec![
|
|
super::PropertyValue {
|
|
display_string: "true".into(),
|
|
code: "true".into(),
|
|
kind: super::PropertyValueKind::Boolean,
|
|
value_bool: true,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "23".into(),
|
|
code: "23".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 23,
|
|
..Default::default()
|
|
},
|
|
]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_struct_of_structs() {
|
|
validate_table_rp(
|
|
"in-out",
|
|
r#"
|
|
struct C1 { c1_1: string, c1_2: int }
|
|
struct C2 { c2_1: string, c2_2: int }
|
|
struct FooStruct { first: C1, second: C2 }
|
|
"#,
|
|
"FooStruct",
|
|
"{ first: { c1_1: \"first of a kind\", c1_2: 23 }, second: { c2_1: \"second of a kind\", c2_2: 42 } }",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Table,
|
|
},
|
|
"{\n \"first\": {\n \"c1-1\": \"first of a kind\",\n \"c1-2\": 23\n },\n \"second\": {\n \"c2-1\": \"second of a kind\",\n \"c2-2\": 42\n }\n}",
|
|
false,
|
|
vec![
|
|
"first.c1-1".into(),
|
|
"first.c1-2".into(),
|
|
"second.c2-1".into(),
|
|
"second.c2-2".into(),
|
|
],
|
|
vec![
|
|
vec![super::PropertyValue {
|
|
display_string: "first of a kind".into(),
|
|
code: "\"first of a kind\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "first of a kind".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "23".into(),
|
|
code: "23".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 23,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "second of a kind".into(),
|
|
code: "\"second of a kind\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "second of a kind".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "42".into(),
|
|
code: "42".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 42,
|
|
..Default::default()
|
|
},
|
|
],
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_array_of_struct_of_structs() {
|
|
validate_table_rp(
|
|
"in-out",
|
|
r#"
|
|
struct C1 { c1_1: string, c1_2: int }
|
|
struct C2 { c2_1: string, c2_2: int }
|
|
struct FooStruct { first: C1, second: C2 }
|
|
"#,
|
|
"[FooStruct]",
|
|
"[{ first: { c1_1: \"first of a kind\", c1_2: 23 }, second: { c2_1: \"second of a kind\", c2_2: 42 } }, { first: { c1_1: \"row 2, 1\", c1_2: 3 }, second: { c2_1: \"row 2, 2\", c2_2: 2 } }]",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Table,
|
|
},
|
|
"[\n {\n \"first\": {\n \"c1-1\": \"first of a kind\",\n \"c1-2\": 23\n },\n \"second\": {\n \"c2-1\": \"second of a kind\",\n \"c2-2\": 42\n }\n },\n {\n \"first\": {\n \"c1-1\": \"row 2, 1\",\n \"c1-2\": 3\n },\n \"second\": {\n \"c2-1\": \"row 2, 2\",\n \"c2-2\": 2\n }\n }\n]",
|
|
true,
|
|
vec![
|
|
"first.c1-1".into(),
|
|
"first.c1-2".into(),
|
|
"second.c2-1".into(),
|
|
"second.c2-2".into(),
|
|
],
|
|
vec![
|
|
vec![super::PropertyValue {
|
|
display_string: "first of a kind".into(),
|
|
code: "\"first of a kind\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "first of a kind".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "23".into(),
|
|
code: "23".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 23,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "second of a kind".into(),
|
|
code: "\"second of a kind\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "second of a kind".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "42".into(),
|
|
code: "42".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 42,
|
|
..Default::default()
|
|
},
|
|
],
|
|
vec![super::PropertyValue {
|
|
display_string: "row 2, 1".into(),
|
|
code: "\"row 2, 1\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "row 2, 1".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "3".into(),
|
|
code: "3".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 3,
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "row 2, 2".into(),
|
|
code: "\"row 2, 2\"".into(),
|
|
kind: super::PropertyValueKind::String,
|
|
value_string: "row 2, 2".into(),
|
|
..Default::default()
|
|
},
|
|
super::PropertyValue {
|
|
display_string: "2".into(),
|
|
code: "2".into(),
|
|
kind: super::PropertyValueKind::Integer,
|
|
value_int: 2,
|
|
..Default::default()
|
|
},
|
|
],
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_map_preview_data_bool_array() {
|
|
validate_table_rp(
|
|
"in-out",
|
|
"",
|
|
"[bool]",
|
|
"[ true, false ]",
|
|
super::PreviewData {
|
|
name: "test".into(),
|
|
has_getter: true,
|
|
has_setter: true,
|
|
kind: super::PreviewDataKind::Table,
|
|
},
|
|
"[\n true,\n false\n]",
|
|
true,
|
|
vec!["".into()],
|
|
vec![
|
|
vec![super::PropertyValue {
|
|
display_string: "true".into(),
|
|
code: "true".into(),
|
|
kind: super::PropertyValueKind::Boolean,
|
|
value_bool: true,
|
|
..Default::default()
|
|
}],
|
|
vec![super::PropertyValue {
|
|
display_string: "false".into(),
|
|
code: "false".into(),
|
|
kind: super::PropertyValueKind::Boolean,
|
|
value_bool: false,
|
|
..Default::default()
|
|
}],
|
|
],
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn validate_array_row_to_struct(indent_level: usize, row: Vec<PropertyValue>, expected: &str) {
|
|
let model = std::rc::Rc::new(VecModel::from(row)).into();
|
|
let received = super::table_row_to_struct(model, indent_level).unwrap();
|
|
|
|
assert_eq!(received, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_table_row_to_stuct() {
|
|
fn bool_pv(value: bool, accessor_path: &str) -> PropertyValue {
|
|
PropertyValue {
|
|
accessor_path: SharedString::from(accessor_path),
|
|
display_string: value.to_shared_string(),
|
|
value_bool: value,
|
|
code: value.to_shared_string(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
validate_array_row_to_struct(0, vec![bool_pv(true, "")], "true");
|
|
validate_array_row_to_struct(1, vec![bool_pv(true, "")], " true");
|
|
validate_array_row_to_struct(2, vec![bool_pv(true, "")], " true");
|
|
validate_array_row_to_struct(3, vec![bool_pv(true, "")], " true");
|
|
validate_array_row_to_struct(
|
|
1,
|
|
vec![bool_pv(true, "test")],
|
|
" {\n \"test\": true\n }",
|
|
);
|
|
validate_array_row_to_struct(
|
|
0,
|
|
vec![bool_pv(true, "l1.l2.l3")],
|
|
"{\n \"l1\": {\n \"l2\": {\n \"l3\": true\n }\n }\n}",
|
|
);
|
|
validate_array_row_to_struct(
|
|
0,
|
|
vec![bool_pv(true, "l1.l2.l3"), bool_pv(false, "l1.test")],
|
|
"{\n \"l1\": {\n \"l2\": {\n \"l3\": true\n },\n \"test\": false\n }\n}",
|
|
);
|
|
}
|
|
}
|