mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 10:50:00 +00:00
Experimental support for MenuBar
Introduces `MenuBar{ ... }` that can be put in a Window
This commit is contained in:
parent
20443ec0df
commit
5bd20def0e
38 changed files with 1023 additions and 69 deletions
|
@ -183,6 +183,31 @@ export component SwipeGestureHandler {
|
|||
//-default_size_binding:expands_to_parent_geometry
|
||||
}
|
||||
|
||||
// Lowered to MenuBarImpl from the style
|
||||
component MenuBar {
|
||||
//-is_non_item_type
|
||||
//-disallow_global_types_as_child_elements
|
||||
in property <[MenuEntry]> entries;
|
||||
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
|
||||
// Currently experimental
|
||||
//-is_internal
|
||||
}
|
||||
|
||||
// Lowered to a call that generate a ContextMenuImpl
|
||||
export component ContextMenu {
|
||||
function show() {}
|
||||
//in property <[MenuEntry]> entries;
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
//-default_size_binding:expands_to_parent_geometry
|
||||
|
||||
// Currently experimental
|
||||
//-is_internal
|
||||
}
|
||||
|
||||
component WindowItem {
|
||||
in-out property <length> width;
|
||||
in-out property <length> height;
|
||||
|
@ -198,7 +223,9 @@ component WindowItem {
|
|||
in property <image> icon;
|
||||
}
|
||||
|
||||
export component Window inherits WindowItem { }
|
||||
export component Window inherits WindowItem {
|
||||
MenuBar {}
|
||||
}
|
||||
|
||||
export component BoxShadow inherits Empty {
|
||||
in property <length> border_radius;
|
||||
|
@ -418,7 +445,7 @@ export component PopupWindow {
|
|||
in property <length> anchor_y;
|
||||
in property <length> anchor_height;
|
||||
in property <length> anchor_width;*/
|
||||
in property <bool> close-on-click;
|
||||
in property <bool> close-on-click;
|
||||
in property <PopupClosePolicy> close-policy; // constexpr hardcoded in typeregister.rs
|
||||
//show() is hardcoded in typeregister.rs
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ pub enum BuiltinFunction {
|
|||
ClearFocusItem,
|
||||
ShowPopupWindow,
|
||||
ClosePopupWindow,
|
||||
ShowPopupMenu,
|
||||
SetSelectionOffsets,
|
||||
/// A function that belongs to an item (such as TextInput's select-all function).
|
||||
ItemMemberFunction(SmolStr),
|
||||
|
@ -156,6 +157,7 @@ declare_builtin_function_types!(
|
|||
ClearFocusItem: (Type::ElementReference) -> Type::Void,
|
||||
ShowPopupWindow: (Type::ElementReference) -> Type::Void,
|
||||
ClosePopupWindow: (Type::ElementReference) -> Type::Void,
|
||||
ShowPopupMenu: (Type::ElementReference, Type::Model, crate::typeregister::logical_point_type()) -> Type::Void,
|
||||
ItemMemberFunction(..): (Type::ElementReference) -> Type::Void,
|
||||
SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
|
||||
ItemFontMetrics: (Type::ElementReference) -> crate::typeregister::font_metrics_type(),
|
||||
|
@ -264,7 +266,9 @@ impl BuiltinFunction {
|
|||
| BuiltinFunction::ATan
|
||||
| BuiltinFunction::ATan2 => true,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => false,
|
||||
BuiltinFunction::SetSelectionOffsets => false,
|
||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties
|
||||
|
@ -331,7 +335,9 @@ impl BuiltinFunction {
|
|||
| BuiltinFunction::ATan
|
||||
| BuiltinFunction::ATan2 => true,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => false,
|
||||
BuiltinFunction::SetSelectionOffsets => false,
|
||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::ItemFontMetrics => true,
|
||||
|
|
|
@ -819,6 +819,24 @@ pub fn generate(
|
|||
}
|
||||
file.declarations.push(Declaration::Struct(globals_struct));
|
||||
|
||||
if let Some(popup_menu) = &llr.popup_menu {
|
||||
let component_id = ident(&popup_menu.item_tree.root.name);
|
||||
let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
|
||||
generate_item_tree(
|
||||
&mut popup_struct,
|
||||
&popup_menu.item_tree,
|
||||
&llr,
|
||||
None,
|
||||
true,
|
||||
component_id,
|
||||
Access::Public,
|
||||
&mut file,
|
||||
&conditional_includes,
|
||||
);
|
||||
file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
|
||||
file.declarations.push(Declaration::Struct(popup_struct));
|
||||
};
|
||||
|
||||
for p in &llr.public_components {
|
||||
generate_public_component(&mut file, &conditional_includes, p, &llr);
|
||||
}
|
||||
|
@ -1199,6 +1217,7 @@ fn generate_public_component(
|
|||
&component.item_tree,
|
||||
unit,
|
||||
None,
|
||||
false,
|
||||
component_id,
|
||||
Access::Private, // Hide properties and other fields from the C++ API
|
||||
file,
|
||||
|
@ -1289,6 +1308,7 @@ fn generate_item_tree(
|
|||
sub_tree: &llr::ItemTree,
|
||||
root: &llr::CompilationUnit,
|
||||
parent_ctx: Option<ParentCtx>,
|
||||
is_popup_menu: bool,
|
||||
item_tree_class_name: SmolStr,
|
||||
field_access: Access,
|
||||
file: &mut File,
|
||||
|
@ -1708,21 +1728,24 @@ fn generate_item_tree(
|
|||
"self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(),
|
||||
];
|
||||
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
if let Some(translations) = &root.translations {
|
||||
let lang_len = translations.languages.len();
|
||||
create_code.push(format!(
|
||||
"std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
|
||||
translations
|
||||
.languages
|
||||
.iter()
|
||||
.map(|l| format!("slint::private_api::string_to_slice({l:?})"))
|
||||
.join(", ")
|
||||
));
|
||||
create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages({{ languages.data(), {lang_len} }});"));
|
||||
}
|
||||
if is_popup_menu {
|
||||
create_code.push("self->globals = globals;".into());
|
||||
create_parameters.push("const SharedGlobals *globals".into());
|
||||
} else if parent_ctx.is_none() {
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
if let Some(translations) = &root.translations {
|
||||
let lang_len = translations.languages.len();
|
||||
create_code.push(format!(
|
||||
"std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
|
||||
translations
|
||||
.languages
|
||||
.iter()
|
||||
.map(|l| format!("slint::private_api::string_to_slice({l:?})"))
|
||||
.join(", ")
|
||||
));
|
||||
create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages({{ languages.data(), {lang_len} }});"));
|
||||
}
|
||||
|
||||
if parent_ctx.is_none() {
|
||||
create_code.push("self->globals = &self->m_globals;".into());
|
||||
create_code.push("self->m_globals.root_weak = self->self_weak;".into());
|
||||
create_code.push("slint::cbindgen_private::slint_ensure_backend();".into());
|
||||
|
@ -1865,6 +1888,7 @@ fn generate_sub_component(
|
|||
&popup.item_tree,
|
||||
root,
|
||||
Some(ParentCtx::new(&ctx, None)),
|
||||
false,
|
||||
component_id,
|
||||
Access::Public,
|
||||
file,
|
||||
|
@ -2404,6 +2428,7 @@ fn generate_repeated_component(
|
|||
&repeated.sub_tree,
|
||||
root,
|
||||
Some(parent_ctx),
|
||||
false,
|
||||
repeater_id.clone(),
|
||||
Access::Public,
|
||||
file,
|
||||
|
@ -3661,6 +3686,53 @@ fn compile_builtin_function_call(
|
|||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinFunction::ShowPopupMenu => {
|
||||
let [llr::Expression::PropertyReference(context_menu_ref), entries, position] = arguments
|
||||
else {
|
||||
panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
|
||||
};
|
||||
|
||||
let context_menu = access_member(context_menu_ref, ctx);
|
||||
let context_menu_rc = access_item_rc(context_menu_ref, ctx);
|
||||
let position = compile_expression(position, ctx);
|
||||
let entries = compile_expression(entries, ctx);
|
||||
let popup = ctx
|
||||
.compilation_unit
|
||||
.popup_menu
|
||||
.as_ref()
|
||||
.expect("there should be a popup menu if we want to show it");
|
||||
let popup_id = ident(&popup.item_tree.root.name);
|
||||
let window = access_window_field(ctx);
|
||||
|
||||
let popup_ctx = EvaluationContext::new_sub_component(
|
||||
ctx.compilation_unit,
|
||||
&popup.item_tree.root,
|
||||
CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
|
||||
None,
|
||||
);
|
||||
let access_entries = access_member(&popup.entries, &popup_ctx);
|
||||
let forward_callback = |pr, cb| {
|
||||
let access = access_member(pr, &popup_ctx);
|
||||
format!("{access}.set_handler(
|
||||
[context_menu](const auto &entry) {{
|
||||
return context_menu->{cb}.call(entry);
|
||||
}});")
|
||||
};
|
||||
let fw_sub_menu = forward_callback(&popup.sub_menu, "sub_menu");
|
||||
let fw_activated = forward_callback(&popup.activated, "activated");
|
||||
let init = format!(r"
|
||||
auto entries = {entries};
|
||||
const slint::cbindgen_private::ContextMenu *context_menu = &({context_menu});
|
||||
{{
|
||||
auto self = popup_menu;
|
||||
{access_entries}.set(std::move(entries));
|
||||
{fw_sub_menu}
|
||||
{fw_activated}
|
||||
}}");
|
||||
|
||||
format!("{window}.show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self](auto popup_menu) {{ {init} }})", globals = ctx.generator_state.global_access)
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
let item = access_member(pr, ctx);
|
||||
|
|
|
@ -186,6 +186,9 @@ pub fn generate(
|
|||
let public_components =
|
||||
llr.public_components.iter().map(|p| generate_public_component(p, &llr));
|
||||
|
||||
let popup_menu =
|
||||
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
|
||||
|
||||
let version_check = format_ident!(
|
||||
"VersionCheck_{}_{}_{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
|
@ -229,6 +232,7 @@ pub fn generate(
|
|||
#(#structs_and_enum_def)*
|
||||
#(#globals)*
|
||||
#(#sub_compos)*
|
||||
#popup_menu
|
||||
#(#public_components)*
|
||||
#shared_globals
|
||||
#(#resource_symbols)*
|
||||
|
@ -249,7 +253,7 @@ fn generate_public_component(
|
|||
let public_component_id = ident(&llr.name);
|
||||
let inner_component_id = inner_component_id(&llr.item_tree.root);
|
||||
|
||||
let component = generate_item_tree(&llr.item_tree, unit, None, None);
|
||||
let component = generate_item_tree(&llr.item_tree, unit, None, None, false);
|
||||
|
||||
let ctx = EvaluationContext {
|
||||
compilation_unit: unit,
|
||||
|
@ -680,7 +684,13 @@ fn generate_sub_component(
|
|||
.popup_windows
|
||||
.iter()
|
||||
.map(|popup| {
|
||||
generate_item_tree(&popup.item_tree, root, Some(ParentCtx::new(&ctx, None)), None)
|
||||
generate_item_tree(
|
||||
&popup.item_tree,
|
||||
root,
|
||||
Some(ParentCtx::new(&ctx, None)),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -1479,6 +1489,7 @@ fn generate_item_tree(
|
|||
root: &llr::CompilationUnit,
|
||||
parent_ctx: Option<ParentCtx>,
|
||||
index_property: Option<llr::PropertyIndex>,
|
||||
is_popup_menu: bool,
|
||||
) -> TokenStream {
|
||||
let sub_comp = generate_sub_component(&sub_tree.root, root, parent_ctx, index_property, true);
|
||||
let inner_component_id = self::inner_component_id(&sub_tree.root);
|
||||
|
@ -1491,11 +1502,14 @@ fn generate_item_tree(
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let globals = if parent_ctx.is_some() {
|
||||
let globals = if is_popup_menu {
|
||||
quote!(globals)
|
||||
} else if parent_ctx.is_some() {
|
||||
quote!(parent.upgrade().unwrap().globals.get().unwrap().clone())
|
||||
} else {
|
||||
quote!(sp::Rc::new(SharedGlobals::new(sp::VRc::downgrade(&self_dyn_rc))))
|
||||
};
|
||||
let globals_arg = is_popup_menu.then(|| quote!(globals: sp::Rc<SharedGlobals>));
|
||||
|
||||
let embedding_function = if parent_ctx.is_some() {
|
||||
quote!(todo!("Components written in Rust can not get embedded yet."))
|
||||
|
@ -1577,7 +1591,7 @@ fn generate_item_tree(
|
|||
#sub_comp
|
||||
|
||||
impl #inner_component_id {
|
||||
pub fn new(#(parent: #parent_component_type)*) -> core::result::Result<sp::VRc<sp::ItemTreeVTable, Self>, slint::PlatformError> {
|
||||
fn new(#(parent: #parent_component_type,)* #globals_arg) -> core::result::Result<sp::VRc<sp::ItemTreeVTable, Self>, slint::PlatformError> {
|
||||
#![allow(unused)]
|
||||
slint::private_unstable_api::ensure_backend()?;
|
||||
let mut _self = Self::default();
|
||||
|
@ -1736,7 +1750,7 @@ fn generate_repeated_component(
|
|||
parent_ctx: ParentCtx,
|
||||
) -> TokenStream {
|
||||
let component =
|
||||
generate_item_tree(&repeated.sub_tree, unit, Some(parent_ctx), repeated.index_prop);
|
||||
generate_item_tree(&repeated.sub_tree, unit, Some(parent_ctx), repeated.index_prop, false);
|
||||
|
||||
let ctx = EvaluationContext {
|
||||
compilation_unit: unit,
|
||||
|
@ -2016,6 +2030,7 @@ fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) ->
|
|||
/// Helper to access a member property/callback of a component.
|
||||
///
|
||||
/// Because the parent can be deleted (issue #3464), this might be an option when accessing the parent
|
||||
#[derive(Clone)]
|
||||
enum MemberAccess {
|
||||
/// The token stream is just an expression to the member
|
||||
Direct(TokenStream),
|
||||
|
@ -2724,6 +2739,68 @@ fn compile_builtin_function_call(
|
|||
panic!("internal error: invalid args to ClosePopupWindow {:?}", arguments)
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ShowPopupMenu => {
|
||||
let [Expression::PropertyReference(context_menu_ref), entries, position] = arguments
|
||||
else {
|
||||
panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
|
||||
};
|
||||
|
||||
let context_menu = access_member(context_menu_ref, ctx);
|
||||
let context_menu_rc = access_item_rc(context_menu_ref, ctx);
|
||||
let position = compile_expression(position, ctx);
|
||||
let entries = compile_expression(entries, ctx);
|
||||
let popup = ctx
|
||||
.compilation_unit
|
||||
.popup_menu
|
||||
.as_ref()
|
||||
.expect("there should be a popup menu if we want to show it");
|
||||
let popup_id = inner_component_id(&popup.item_tree.root);
|
||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
|
||||
let popup_ctx = EvaluationContext::new_sub_component(
|
||||
ctx.compilation_unit,
|
||||
&popup.item_tree.root,
|
||||
RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()) },
|
||||
None,
|
||||
);
|
||||
let access_entries = access_member(&popup.entries, &popup_ctx).unwrap();
|
||||
let forward_callback = |pr, cb| {
|
||||
let access = access_member(pr, &popup_ctx).unwrap();
|
||||
let call = context_menu
|
||||
.clone()
|
||||
.map_or_default(|context_menu| quote!(#context_menu.#cb.call(entry)));
|
||||
quote!(
|
||||
let self_weak = parent_weak.clone();
|
||||
#access.set_handler(move |entry| {
|
||||
let self_rc = self_weak.upgrade().unwrap();
|
||||
let _self = self_rc.as_pin_ref();
|
||||
#call
|
||||
}
|
||||
))
|
||||
};
|
||||
let fw_sub_menu = forward_callback(&popup.sub_menu, quote!(sub_menu));
|
||||
let fw_activated = forward_callback(&popup.activated, quote!(activated));
|
||||
quote!({
|
||||
let entries = #entries;
|
||||
let position = #position;
|
||||
let popup_instance = #popup_id::new(_self.globals.get().unwrap().clone()).unwrap();
|
||||
let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x);
|
||||
let parent_weak = _self.self_weak.get().unwrap().clone();
|
||||
{
|
||||
let _self = popup_instance_vrc.as_pin_ref();
|
||||
#access_entries.set(entries);
|
||||
#fw_sub_menu;
|
||||
#fw_activated;
|
||||
};
|
||||
#popup_id::user_init(popup_instance_vrc.clone());
|
||||
sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup(
|
||||
&sp::VRc::into_dyn(popup_instance.into()),
|
||||
position,
|
||||
sp::PopupClosePolicy::CloseOnClickOutside,
|
||||
#context_menu_rc,
|
||||
);
|
||||
})
|
||||
}
|
||||
BuiltinFunction::SetSelectionOffsets => {
|
||||
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
||||
let item = access_member(pr, ctx);
|
||||
|
|
|
@ -380,7 +380,7 @@ impl BuiltinPropertyInfo {
|
|||
}
|
||||
|
||||
/// The base of an element
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, derive_more::From)]
|
||||
pub enum ElementType {
|
||||
/// The element is based of a component
|
||||
Component(Rc<Component>),
|
||||
|
|
|
@ -275,6 +275,14 @@ pub struct PopupWindow {
|
|||
pub position: MutExpression,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PopupMenu {
|
||||
pub item_tree: ItemTree,
|
||||
pub sub_menu: PropertyReference,
|
||||
pub activated: PropertyReference,
|
||||
pub entries: PropertyReference,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timer {
|
||||
pub interval: MutExpression,
|
||||
|
@ -348,7 +356,7 @@ impl std::fmt::Debug for SubComponentInstance {
|
|||
pub struct ItemTree {
|
||||
pub root: SubComponent,
|
||||
pub tree: TreeNode,
|
||||
/// This tree has a parent. e.g: it is a Repeater or a PopupMenu whose property can access
|
||||
/// This tree has a parent. e.g: it is a Repeater or a PopupWindow whose property can access
|
||||
/// the parent ItemTree.
|
||||
/// The String is the type of the parent ItemTree
|
||||
pub parent_context: Option<SmolStr>,
|
||||
|
@ -367,6 +375,7 @@ pub struct CompilationUnit {
|
|||
pub public_components: Vec<PublicComponent>,
|
||||
pub sub_components: Vec<Rc<SubComponent>>,
|
||||
pub globals: Vec<GlobalComponent>,
|
||||
pub popup_menu: Option<PopupMenu>,
|
||||
pub has_debug_info: bool,
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
pub translations: Option<super::translations::Translations>,
|
||||
|
@ -408,6 +417,9 @@ impl CompilationUnit {
|
|||
for p in &self.public_components {
|
||||
visit_component(self, &p.item_tree.root, visitor, None);
|
||||
}
|
||||
if let Some(p) = &self.popup_menu {
|
||||
visit_component(self, &p.item_tree.root, visitor, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each_expression<'a>(
|
||||
|
|
|
@ -62,6 +62,30 @@ pub fn lower_to_item_tree(
|
|||
})
|
||||
.collect();
|
||||
|
||||
let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
|
||||
let sc = lower_sub_component(&c, &state, None, compiler_config);
|
||||
let item_tree = ItemTree {
|
||||
tree: make_tree(&state, &c.root_element, &sc, &[]),
|
||||
root: Rc::try_unwrap(sc.sub_component).unwrap(),
|
||||
parent_context: None,
|
||||
};
|
||||
PopupMenu {
|
||||
item_tree,
|
||||
sub_menu: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
|
||||
&state,
|
||||
),
|
||||
activated: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
|
||||
&state,
|
||||
),
|
||||
entries: sc.mapping.map_property_reference(
|
||||
&NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
|
||||
&state,
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
let root = CompilationUnit {
|
||||
public_components,
|
||||
globals,
|
||||
|
@ -75,6 +99,7 @@ pub fn lower_to_item_tree(
|
|||
})
|
||||
.collect(),
|
||||
has_debug_info: compiler_config.debug_info,
|
||||
popup_menu,
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
translations: state.translation_builder.take().map(|x| x.into_inner().result()),
|
||||
};
|
||||
|
@ -691,7 +716,7 @@ fn lower_popup_component(
|
|||
|
||||
use super::Expression::PropertyReference as PR;
|
||||
let position = super::lower_expression::make_struct(
|
||||
"Point",
|
||||
"LogicalPosition",
|
||||
[
|
||||
("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
|
||||
("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
|
||||
|
|
|
@ -97,7 +97,9 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
|||
BuiltinFunction::Log => 10,
|
||||
BuiltinFunction::Pow => 10,
|
||||
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => isize::MAX,
|
||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
|
||||
BuiltinFunction::ShowPopupWindow
|
||||
| BuiltinFunction::ClosePopupWindow
|
||||
| BuiltinFunction::ShowPopupMenu => isize::MAX,
|
||||
BuiltinFunction::SetSelectionOffsets => isize::MAX,
|
||||
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
||||
BuiltinFunction::ItemFontMetrics => PROPERTY_ACCESS_COST,
|
||||
|
|
|
@ -390,6 +390,7 @@ mod plural_rule_parser {
|
|||
globals: Vec::new(),
|
||||
has_debug_info: false,
|
||||
translations: None,
|
||||
popup_menu: None,
|
||||
},
|
||||
current_sub_component: None,
|
||||
current_global: None,
|
||||
|
|
|
@ -62,6 +62,9 @@ pub struct Document {
|
|||
|
||||
/// The list of used extra types used recursively.
|
||||
pub used_types: RefCell<UsedSubTypes>,
|
||||
|
||||
/// The popup_menu_impl
|
||||
pub popup_menu_impl: Option<Rc<Component>>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
|
@ -243,6 +246,7 @@ impl Document {
|
|||
exports,
|
||||
embedded_file_resources: Default::default(),
|
||||
used_types: Default::default(),
|
||||
popup_menu_impl: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,6 +276,9 @@ impl Document {
|
|||
for c in &used_types.globals {
|
||||
v(c);
|
||||
}
|
||||
if let Some(c) = &self.popup_menu_impl {
|
||||
v(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ mod lower_absolute_coordinates;
|
|||
mod lower_accessibility;
|
||||
mod lower_component_container;
|
||||
mod lower_layout;
|
||||
mod lower_menus;
|
||||
mod lower_popups;
|
||||
mod lower_property_to_element;
|
||||
mod lower_shadows;
|
||||
|
@ -96,6 +97,7 @@ pub async fn run_passes(
|
|||
);
|
||||
});
|
||||
lower_tabwidget::lower_tabwidget(doc, type_loader, diag).await;
|
||||
lower_menus::lower_menus(doc, type_loader, diag).await;
|
||||
collect_subcomponents::collect_subcomponents(doc);
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
|
|
|
@ -24,12 +24,10 @@ pub fn collect_globals(doc: &Document, _diag: &mut BuildDiagnostics) {
|
|||
}
|
||||
}
|
||||
}
|
||||
for component in doc.exported_roots() {
|
||||
collect_in_component(&component, &mut set, &mut sorted_globals);
|
||||
}
|
||||
for component in &doc.used_types.borrow().sub_components {
|
||||
collect_in_component(component, &mut set, &mut sorted_globals);
|
||||
}
|
||||
doc.visit_all_used_components(|component| {
|
||||
collect_in_component(component, &mut set, &mut sorted_globals)
|
||||
});
|
||||
|
||||
doc.used_types.borrow_mut().globals = sorted_globals;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::rc::Rc;
|
|||
pub fn collect_subcomponents(doc: &Document) {
|
||||
let mut result = vec![];
|
||||
let mut hash = HashSet::new();
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
collect_subcomponents_recursive(&component, &mut result, &mut hash);
|
||||
}
|
||||
doc.used_types.borrow_mut().sub_components = result;
|
||||
|
|
|
@ -59,11 +59,11 @@ pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut Buil
|
|||
}
|
||||
let mut roots = HashSet::new();
|
||||
if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
roots.insert(ByAddress(component.clone()));
|
||||
}
|
||||
}
|
||||
for component in doc.exported_roots() {
|
||||
for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
|
||||
inline_components_recursively(&component, &roots, inline_selection, diag);
|
||||
let mut init_code = component.init_code.borrow_mut();
|
||||
let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
|
||||
|
|
164
internal/compiler/passes/lower_menus.rs
Normal file
164
internal/compiler/passes/lower_menus.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
// 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
|
||||
|
||||
//! Passe lower the `MenuBar` and `ContextMenu` as well as all their contents
|
||||
//!
|
||||
//! Must be done before inlining and many other passes because the lowered code must
|
||||
//! be further inlined as it may expends to native widget that needs inlining
|
||||
|
||||
use crate::diagnostics::BuildDiagnostics;
|
||||
use crate::expression_tree::NamedReference;
|
||||
use crate::langtype::ElementType;
|
||||
use crate::object_tree::*;
|
||||
use smol_str::{format_smolstr, SmolStr};
|
||||
|
||||
struct UsefulMenuComponents {
|
||||
menubar_impl: ElementType,
|
||||
vertical_layout: ElementType,
|
||||
empty: ElementType,
|
||||
}
|
||||
|
||||
pub async fn lower_menus(
|
||||
doc: &mut Document,
|
||||
type_loader: &mut crate::typeloader::TypeLoader,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) {
|
||||
// Ignore import errors
|
||||
let mut build_diags_to_ignore = BuildDiagnostics::default();
|
||||
|
||||
let menubar_impl = type_loader
|
||||
.import_component("std-widgets.slint", "MenuBarImpl", &mut build_diags_to_ignore)
|
||||
.await
|
||||
.expect("MenuBarImpl should be in std-widgets.slint");
|
||||
let useful_menu_component = UsefulMenuComponents {
|
||||
menubar_impl: menubar_impl.clone().into(),
|
||||
vertical_layout: type_loader
|
||||
.global_type_registry
|
||||
.borrow()
|
||||
.lookup_builtin_element("VerticalLayout")
|
||||
.expect("VerticalLayout is a builtin type"),
|
||||
empty: type_loader.global_type_registry.borrow().empty_type(),
|
||||
};
|
||||
|
||||
let mut has_menu = false;
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "Window") {
|
||||
has_menu |= process_window(elem, &useful_menu_component, diag);
|
||||
}
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
||||
has_menu |= process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if has_menu {
|
||||
let popup_menu_impl = type_loader
|
||||
.import_component("std-widgets.slint", "PopupMenuImpl", &mut build_diags_to_ignore)
|
||||
.await
|
||||
.expect("PopupMenuImpl should be in std-widgets.slint");
|
||||
{
|
||||
let mut root = popup_menu_impl.root_element.borrow_mut();
|
||||
|
||||
for prop in ["entries", "sub-menu", "activated"] {
|
||||
match root.property_declarations.get_mut(prop) {
|
||||
Some(d) => d.expose_in_public_api = true,
|
||||
None => diag.push_error(format!("PopupMenuImpl doesn't have {prop}"), &*root),
|
||||
}
|
||||
}
|
||||
root.property_analysis.borrow_mut().entry("entries".into()).or_default().is_set = true;
|
||||
}
|
||||
|
||||
recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
||||
process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
});
|
||||
recurse_elem_including_sub_components_no_borrow(&menubar_impl, &(), &mut |elem, _| {
|
||||
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
||||
process_context_menu(elem, &useful_menu_component, diag);
|
||||
}
|
||||
});
|
||||
doc.popup_menu_impl = popup_menu_impl.into();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_context_menu(
|
||||
_context_menu_elem: &ElementRc,
|
||||
_useful_menu_components: &UsefulMenuComponents,
|
||||
_diag: &mut BuildDiagnostics,
|
||||
) -> bool {
|
||||
// TODO:
|
||||
true
|
||||
}
|
||||
|
||||
fn process_window(
|
||||
win: &ElementRc,
|
||||
components: &UsefulMenuComponents,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) -> bool {
|
||||
/* if matches!(&elem.borrow_mut().base_type, ElementType::Builtin(_)) {
|
||||
// That's the TabWidget re-exported from the style, it doesn't need to be processed
|
||||
return;
|
||||
}*/
|
||||
|
||||
let mut window = win.borrow_mut();
|
||||
let mut menu_bar = None;
|
||||
window.children.retain(|x| {
|
||||
if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") {
|
||||
if menu_bar.is_some() {
|
||||
diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow());
|
||||
} else {
|
||||
menu_bar = Some(x.clone());
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
let Some(menu_bar) = menu_bar else {
|
||||
return false;
|
||||
};
|
||||
menu_bar.borrow_mut().base_type = components.menubar_impl.clone();
|
||||
|
||||
// Create a child that contains all the child but the menubar
|
||||
let child = Element {
|
||||
id: format_smolstr!("{}-child", window.id),
|
||||
base_type: components.empty.clone(),
|
||||
enclosing_component: window.enclosing_component.clone(),
|
||||
children: std::mem::take(&mut window.children),
|
||||
..Default::default()
|
||||
}
|
||||
.make_rc();
|
||||
|
||||
const HEIGHT: &str = "height";
|
||||
let child_height = NamedReference::new(&child, SmolStr::new_static(HEIGHT));
|
||||
|
||||
// Create a layout
|
||||
let layout = Element {
|
||||
id: format_smolstr!("{}-menulayout", window.id),
|
||||
base_type: components.vertical_layout.clone(),
|
||||
enclosing_component: window.enclosing_component.clone(),
|
||||
children: vec![menu_bar, child],
|
||||
..Default::default()
|
||||
}
|
||||
.make_rc();
|
||||
|
||||
window.children.push(layout);
|
||||
let component = window.enclosing_component.upgrade().unwrap();
|
||||
|
||||
drop(window);
|
||||
|
||||
// Rename every access to `root.height` into `child.height`
|
||||
let win_height = NamedReference::new(win, SmolStr::new_static(HEIGHT));
|
||||
crate::object_tree::visit_all_named_references(&component, &mut |nr| {
|
||||
if nr == &win_height {
|
||||
*nr = child_height.clone()
|
||||
}
|
||||
});
|
||||
// except for the actual geometry
|
||||
win.borrow_mut().geometry_props.as_mut().unwrap().height = win_height;
|
||||
true
|
||||
}
|
|
@ -13,13 +13,11 @@ use std::rc::Rc;
|
|||
/// It currently does so by adding a number to the existing id
|
||||
pub fn assign_unique_id(doc: &Document) {
|
||||
let mut count = 0;
|
||||
for component in doc.exported_roots() {
|
||||
assign_unique_id_in_component(&component, &mut count);
|
||||
}
|
||||
for c in &doc.used_types.borrow().sub_components {
|
||||
assign_unique_id_in_component(c, &mut count);
|
||||
}
|
||||
|
||||
doc.visit_all_used_components(|component| {
|
||||
if !component.is_global() {
|
||||
assign_unique_id_in_component(component, &mut count)
|
||||
}
|
||||
});
|
||||
rename_globals(doc, count);
|
||||
}
|
||||
|
||||
|
|
32
internal/compiler/tests/syntax/elements/menubar.slint
Normal file
32
internal/compiler/tests/syntax/elements/menubar.slint
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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
|
||||
|
||||
|
||||
export component A inherits Window {
|
||||
mb := MenuBar {
|
||||
}
|
||||
MenuBar {
|
||||
// ^error{Only one MenuBar is allowed in a Window}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 45px;
|
||||
}
|
||||
Rectangle {
|
||||
x: mb.absolute-position.x;
|
||||
// ^error{Element 'MenuBar' does not have a property 'absolute-position'}
|
||||
y: mb.height;
|
||||
// ^error{Element 'MenuBar' does not have a property 'height'}
|
||||
init => {
|
||||
mb.focus();
|
||||
// ^error{Element 'MenuBar' does not have a property 'focus'}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// TESTS TODO
|
||||
// - test that setting the window height sets it to the window height plus that of the menubar
|
30
internal/compiler/tests/syntax/elements/menubar2.slint
Normal file
30
internal/compiler/tests/syntax/elements/menubar2.slint
Normal file
|
@ -0,0 +1,30 @@
|
|||
// 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
|
||||
|
||||
|
||||
export component MyMenu inherits MenuBar {
|
||||
// ^error{MenuBar can only be within a Window element}
|
||||
}
|
||||
|
||||
export component A inherits Window {
|
||||
MenuBar {
|
||||
Rectangle {}
|
||||
// ^error{MenuBar cannot have children elements}
|
||||
x: 45px;
|
||||
// ^error{Unknown property x in MenuBar}
|
||||
width: 45px;
|
||||
// ^error{Unknown property width in MenuBar}
|
||||
entries: [];
|
||||
init => {}
|
||||
// ^error{'init' is not a callback in MenuBar}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 45px;
|
||||
MenuBar {
|
||||
// ^error{MenuBar can only be within a Window element}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,6 +261,9 @@ impl Snapshotter {
|
|||
document.inner_components.iter().for_each(|ic| {
|
||||
let _ = self.create_component(ic);
|
||||
});
|
||||
if let Some(popup_menu_impl) = &document.popup_menu_impl {
|
||||
let _ = self.create_component(popup_menu_impl);
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
|
||||
|
@ -284,6 +287,10 @@ impl Snapshotter {
|
|||
exports,
|
||||
embedded_file_resources: document.embedded_file_resources.clone(),
|
||||
used_types: RefCell::new(self.snapshot_used_sub_types(&document.used_types.borrow())),
|
||||
popup_menu_impl: document.popup_menu_impl.as_ref().map(|p| {
|
||||
Weak::upgrade(&self.use_component(p))
|
||||
.expect("Components can get upgraded at this point")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -399,6 +399,7 @@ impl TypeRegister {
|
|||
($pub_type:ident, i32) => { Type::Int32 };
|
||||
($pub_type:ident, f32) => { Type::Float32 };
|
||||
($pub_type:ident, SharedString) => { Type::String };
|
||||
($pub_type:ident, Image) => { Type::Image };
|
||||
($pub_type:ident, Coord) => { Type::LogicalLength };
|
||||
($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
|
||||
($pub_type:ident, $_:ident) => {
|
||||
|
@ -472,6 +473,19 @@ impl TypeRegister {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match &mut register.elements.get_mut("ContextMenu").unwrap() {
|
||||
ElementType::Builtin(ref mut b) => {
|
||||
let b = Rc::get_mut(b).unwrap();
|
||||
b.properties.insert(
|
||||
"show".into(),
|
||||
BuiltinPropertyInfo::new(Type::Function(BuiltinFunction::ShowPopupMenu.ty())),
|
||||
);
|
||||
b.member_functions.insert("show".into(), BuiltinFunction::ShowPopupMenu);
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let font_metrics_prop = crate::langtype::BuiltinPropertyInfo {
|
||||
ty: font_metrics_type(),
|
||||
property_visibility: PropertyVisibility::Output,
|
||||
|
|
112
internal/compiler/widgets/common/menus.slint
Normal file
112
internal/compiler/widgets/common/menus.slint
Normal file
|
@ -0,0 +1,112 @@
|
|||
// 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
|
||||
|
||||
//! This file contains a generic implementation of the MenuBar and ContextMenu
|
||||
|
||||
import { Palette } from "std-widgets-impl.slint";
|
||||
|
||||
export component PopupMenuImpl inherits Window {
|
||||
property <length> px: 1rem / 14;
|
||||
in property <[MenuEntry]> entries: [];
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
callback activated(MenuEntry);
|
||||
|
||||
Rectangle {
|
||||
border-radius: 7*px;
|
||||
border-color: Palette.border;
|
||||
background: Palette.background;
|
||||
drop-shadow-blur: 2px;
|
||||
drop-shadow-color: Palette.foreground.transparentize(0.5);
|
||||
min-width: 10rem;
|
||||
VerticalLayout {
|
||||
padding: 5px;
|
||||
for entry in entries: Rectangle {
|
||||
background: ita.has-hover || ita.pressed ? Palette.alternate-background : transparent;
|
||||
border-radius: 3*px;
|
||||
border-width: 1px;
|
||||
HorizontalLayout {
|
||||
spacing: 7*px;
|
||||
padding: 11*px;
|
||||
padding-top: 4*px;
|
||||
padding-bottom: 6*px;
|
||||
Text {
|
||||
text: entry.title;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
if entry.has-sub-menu : Text {
|
||||
text: "▶";
|
||||
horizontal-stretch: 0;
|
||||
}
|
||||
}
|
||||
ita := TouchArea {
|
||||
clicked => {
|
||||
if entry.has-sub-menu {
|
||||
subMenu.show(sub-menu(entry), {
|
||||
x: root.width,
|
||||
y: self.absolute-position.y - subMenu.absolute-position.y,
|
||||
});
|
||||
} else {
|
||||
activated(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subMenu := ContextMenu {
|
||||
x: 0; y: 0; width: 0; height: 0;
|
||||
sub-menu(entry) => { root.sub-menu(entry); }
|
||||
activated(entry) => { root.activated(entry); }
|
||||
}
|
||||
}
|
||||
|
||||
export component MenuBarImpl {
|
||||
callback activated(MenuEntry);
|
||||
callback sub-menu(MenuEntry) -> [MenuEntry];
|
||||
|
||||
property <[MenuEntry]> entries;
|
||||
|
||||
property <length> px: 1rem / 14;
|
||||
|
||||
preferred-width: 100%;
|
||||
height: l.preferred-height;
|
||||
l := HorizontalLayout {
|
||||
padding: 5*px;
|
||||
alignment: start;
|
||||
spacing: 1*px;
|
||||
for entry in entries: e := Rectangle {
|
||||
background: ta.has-hover || ta.pressed ? Palette.alternate-background : transparent;
|
||||
border-radius: 3*px;
|
||||
HorizontalLayout {
|
||||
padding: 11*px;
|
||||
padding-top: 4*px;
|
||||
padding-bottom: 6*px;
|
||||
Text {
|
||||
text: entry.title;
|
||||
}
|
||||
}
|
||||
ta := TouchArea {
|
||||
clicked => {
|
||||
cm.show(sub-menu(entry), { x: e.x, y: root.height });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the default size when there is no entries
|
||||
Rectangle {
|
||||
HorizontalLayout {
|
||||
padding-top: 0.4rem;
|
||||
padding-bottom: 0.6rem;
|
||||
Text { text: ""; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cm := ContextMenu {
|
||||
activated(entry) => { root.activated(entry); }
|
||||
sub-menu(entry) => { root.sub-menu(entry); }
|
||||
}
|
||||
}
|
||||
|
|
@ -22,5 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "datepicker.slint";
|
||||
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
@ -22,4 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
@ -22,4 +22,5 @@ export { Switch } from "switch.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
export * from "tableview.slint";
|
||||
|
|
|
@ -21,3 +21,4 @@ export { Spinner } from "spinner.slint";
|
|||
export { TextEdit } from "textedit.slint";
|
||||
export { TimePickerPopup, Time } from "time-picker.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
|
@ -20,3 +20,4 @@ export { TimePickerPopup, Time } from "time-picker.slint";
|
|||
export { StandardListView, ListView } from "../common/listview.slint";
|
||||
export { TextEdit } from "textedit.slint";
|
||||
export { DatePickerPopup, Date } from "./datepicker.slint";
|
||||
export { MenuBarImpl, PopupMenuImpl } from "../common/menus.slint";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue