use alloc::rc::Rc;
use core::cell::RefCell;
use crate::core::{ObjectId, Orientation};
use crate::platform::WidgetTriggerKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CheckState {
Unchecked,
Checked,
PartiallyChecked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EchoMode {
Normal,
Password,
NoEcho,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SelectionMode {
Single,
Multi,
Extended,
None,
}
pub trait ListModel {
fn row_count(&self) -> usize;
fn text(&self, row: usize, col: usize) -> String;
fn set_text(&mut self, row: usize, col: usize, text: &str);
}
pub type ClickCallback = Rc<RefCell<dyn FnMut()>>;
pub type ValueChangedCallback = Rc<RefCell<dyn FnMut(String)>>;
pub trait WidgetHandle: Sized {
fn raw_id(&self) -> ObjectId;
fn from_raw(id: ObjectId) -> Self;
fn show(&self) {
crate::show_widget(self.raw_id());
}
fn hide(&self) {
crate::hide_widget(self.raw_id());
}
fn set_geometry(&self, x: i32, y: i32, w: u32, h: u32) {
crate::set_widget_geometry(self.raw_id(), x, y, w, h);
}
fn set_text(&self, text: &str) {
crate::set_widget_text(self.raw_id(), text);
}
fn text(&self) -> String {
crate::get_widget_text(self.raw_id())
}
fn enable(&self) {
crate::set_widget_enabled(self.raw_id(), true);
}
fn disable(&self) {
crate::set_widget_enabled(self.raw_id(), false);
}
fn is_enabled(&self) -> bool {
crate::is_widget_enabled(self.raw_id())
}
fn set_visible(&self, visible: bool) {
crate::set_widget_visible(self.raw_id(), visible);
}
fn is_visible(&self) -> bool {
crate::is_widget_visible(self.raw_id())
}
fn on_click<F: FnMut() + 'static>(&self, f: F);
fn on_value_changed<F: FnMut(String) + 'static>(&self, f: F);
}
use std::collections::HashMap;
thread_local! {
static CLICK_CALLBACKS: RefCell<HashMap<ObjectId, ClickCallback>> = RefCell::new(HashMap::new());
static VALUE_CALLBACKS: RefCell<HashMap<ObjectId, ValueChangedCallback>> = RefCell::new(HashMap::new());
}
pub fn remove_callbacks(id: ObjectId) {
CLICK_CALLBACKS.with(|map| {
map.borrow_mut().remove(&id);
});
VALUE_CALLBACKS.with(|map| {
map.borrow_mut().remove(&id);
});
}
pub fn dispatch_trigger(widget_id: ObjectId, kind: WidgetTriggerKind) -> bool {
match kind {
WidgetTriggerKind::Clicked | WidgetTriggerKind::Unknown => CLICK_CALLBACKS.with(|map| {
let mut map = map.borrow_mut();
if let Some(cb) = map.get_mut(&widget_id) {
(cb.borrow_mut())();
true
} else {
false
}
}),
WidgetTriggerKind::ValueChanged | WidgetTriggerKind::SelectionChanged => {
let text = crate::get_widget_text(widget_id);
VALUE_CALLBACKS.with(|map| {
let mut map = map.borrow_mut();
if let Some(cb) = map.get_mut(&widget_id) {
(cb.borrow_mut())(text);
true
} else {
false
}
})
}
WidgetTriggerKind::Closed => {
remove_callbacks(widget_id);
false
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WindowHandle {
id: ObjectId,
}
impl WindowHandle {
pub fn from_raw(id: ObjectId) -> Self {
Self { id }
}
pub fn raw_id(&self) -> ObjectId {
self.id
}
}
impl WidgetHandle for WindowHandle {
fn raw_id(&self) -> ObjectId {
self.id
}
fn from_raw(id: ObjectId) -> Self {
Self { id }
}
fn on_click<F: FnMut() + 'static>(&self, f: F) {
CLICK_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
fn on_value_changed<F: FnMut(String) + 'static>(&self, f: F) {
VALUE_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
}
impl Drop for WindowHandle {
fn drop(&mut self) {
remove_callbacks(self.id);
}
}
impl WindowHandle {
pub fn set_title(&self, title: &str) {
crate::set_widget_text(self.id, title);
}
pub fn new_button(&self, text: &str, x: i32, y: i32, w: u32, h: u32) -> ButtonHandle {
ButtonHandle::from_raw(crate::create_button(self.id, text, x, y, w, h))
}
pub fn new_label(&self, text: &str, x: i32, y: i32, w: u32, h: u32) -> LabelHandle {
LabelHandle::from_raw(crate::create_label(self.id, text, x, y, w, h))
}
pub fn new_checkbox(&self, text: &str, x: i32, y: i32, w: u32, h: u32) -> CheckBoxHandle {
CheckBoxHandle::from_raw(crate::create_checkbox(self.id, text, x, y, w, h))
}
pub fn new_radio_button(
&self,
text: &str,
x: i32,
y: i32,
w: u32,
h: u32,
) -> RadioButtonHandle {
RadioButtonHandle::from_raw(crate::create_radio_button(self.id, text, x, y, w, h))
}
pub fn new_line_edit(&self, text: &str, x: i32, y: i32, w: u32, h: u32) -> LineEditHandle {
LineEditHandle::from_raw(crate::create_line_edit(self.id, text, x, y, w, h))
}
pub fn new_combo_box(&self, x: i32, y: i32, w: u32, h: u32) -> ComboBoxHandle {
ComboBoxHandle::from_raw(crate::create_combo_box(self.id, x, y, w, h))
}
pub fn new_list_box(&self, x: i32, y: i32, w: u32, h: u32) -> ListBoxHandle {
ListBoxHandle::from_raw(crate::create_list_box(self.id, x, y, w, h))
}
pub fn new_slider(&self, x: i32, y: i32, w: u32, h: u32) -> SliderHandle {
SliderHandle::from_raw(crate::create_slider(self.id, x, y, w, h))
}
pub fn new_progress_bar(&self, x: i32, y: i32, w: u32, h: u32) -> ProgressBarHandle {
ProgressBarHandle::from_raw(crate::create_progress_bar(self.id, x, y, w, h))
}
pub fn new_panel(&self, x: i32, y: i32, w: u32, h: u32) -> PanelHandle {
PanelHandle::from_raw(crate::create_panel(self.id, x, y, w, h))
}
pub fn new_spin_box(&self, x: i32, y: i32, w: u32, h: u32) -> SpinBoxHandle {
SpinBoxHandle::from_raw(crate::create_spin_box(self.id, x, y, w, h))
}
pub fn new_list_view(&self, x: i32, y: i32, w: u32, h: u32) -> ListViewHandle {
ListViewHandle::from_raw(crate::create_list_view(self.id, x, y, w, h))
}
pub fn new_scroll_area(&self, x: i32, y: i32, w: u32, h: u32) -> ScrollAreaHandle {
ScrollAreaHandle::from_raw(crate::create_scroll_area(self.id, x, y, w, h))
}
pub fn new_message_box(
&self,
title: &str,
text: &str,
x: i32,
y: i32,
w: u32,
h: u32,
) -> MessageBoxHandle {
MessageBoxHandle::from_raw(crate::create_message_box(self.id, title, text, x, y, w, h))
}
pub fn set_layout(&self, layout: impl crate::layout::Layout + 'static) {
LAYOUTS.with(|map| {
map.borrow_mut().insert(self.id, Box::new(layout));
});
}
}
thread_local! {
static LAYOUTS: RefCell<HashMap<ObjectId, Box<dyn crate::layout::Layout>>> = RefCell::new(HashMap::new());
}
macro_rules! impl_handle {
($name:ident, $doc:expr) => {
#[doc = $doc]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name {
id: ObjectId,
}
impl $name {
pub fn from_raw(id: ObjectId) -> Self {
Self { id }
}
}
impl WidgetHandle for $name {
fn raw_id(&self) -> ObjectId {
self.id
}
fn from_raw(id: ObjectId) -> Self {
Self { id }
}
fn on_click<F: FnMut() + 'static>(&self, f: F) {
CLICK_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
fn on_value_changed<F: FnMut(String) + 'static>(&self, f: F) {
VALUE_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
}
impl Drop for $name {
fn drop(&mut self) {
remove_callbacks(self.id);
}
}
};
}
impl_handle!(ButtonHandle, "Type-safe handle for a Button widget.");
impl_handle!(LabelHandle, "Type-safe handle for a Label widget.");
impl_handle!(CheckBoxHandle, "Type-safe handle for a CheckBox widget.");
impl_handle!(RadioButtonHandle, "Type-safe handle for a RadioButton widget.");
impl_handle!(LineEditHandle, "Type-safe handle for a LineEdit widget.");
impl_handle!(ComboBoxHandle, "Type-safe handle for a ComboBox widget.");
impl_handle!(ListBoxHandle, "Type-safe handle for a ListBox widget.");
impl_handle!(SliderHandle, "Type-safe handle for a Slider widget.");
impl_handle!(ProgressBarHandle, "Type-safe handle for a ProgressBar widget.");
impl_handle!(PanelHandle, "Type-safe handle for a Panel widget.");
impl_handle!(SpinBoxHandle, "Type-safe handle for a SpinBox widget.");
impl_handle!(ListViewHandle, "Type-safe handle for a ListView widget.");
impl_handle!(ScrollAreaHandle, "Type-safe handle for a ScrollArea widget.");
impl_handle!(TextEditHandle, "Type-safe handle for a TextEdit (multi-line text) widget.");
impl_handle!(ScrollBarHandle, "Type-safe handle for a ScrollBar widget.");
impl_handle!(TabWidgetHandle, "Type-safe handle for a TabWidget (tab container) widget.");
impl_handle!(GridWidgetHandle, "Type-safe handle for a GridWidget (grid layout) widget.");
impl_handle!(FrameHandle, "Type-safe handle for a Frame widget.");
impl_handle!(DialogHandle, "Type-safe handle for a generic Dialog widget.");
impl_handle!(WebViewHandle, "Type-safe handle for a WebView (web content) widget.");
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MessageBoxHandle {
id: ObjectId,
}
impl MessageBoxHandle {
pub fn from_raw(id: ObjectId) -> Self {
Self { id }
}
pub fn raw_id(&self) -> ObjectId {
self.id
}
pub fn show_modal(&self) {
crate::show_widget(self.id);
}
pub fn close(&self) {
crate::hide_widget(self.id);
}
pub fn set_title(&self, title: &str) {
crate::set_widget_text(self.id, title);
}
}
impl WidgetHandle for MessageBoxHandle {
fn raw_id(&self) -> ObjectId {
self.id
}
fn from_raw(id: ObjectId) -> Self {
Self { id }
}
fn on_click<F: FnMut() + 'static>(&self, f: F) {
CLICK_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
fn on_value_changed<F: FnMut(String) + 'static>(&self, f: F) {
VALUE_CALLBACKS.with(|map| {
map.borrow_mut().insert(self.id, Rc::new(RefCell::new(f)));
});
}
}
impl Drop for MessageBoxHandle {
fn drop(&mut self) {
remove_callbacks(self.id);
}
}
impl ComboBoxHandle {
pub fn add_item(&self, text: &str) -> bool {
crate::combo_box_add_item(self.raw_id(), text)
}
pub fn clear_items(&self) -> bool {
crate::combo_box_clear_items(self.raw_id())
}
pub fn set_current_index(&self, index: usize) -> bool {
crate::combo_box_set_current_index(self.raw_id(), index)
}
pub fn current_index(&self) -> Option<usize> {
crate::combo_box_current_index(self.raw_id())
}
pub fn item_count(&self) -> usize {
crate::combo_box_item_count(self.raw_id())
}
pub fn item_text(&self, index: usize) -> Option<String> {
crate::combo_box_item_text(self.raw_id(), index)
}
}
impl ListBoxHandle {
pub fn add_item(&self, text: &str) -> bool {
crate::list_box_add_item(self.raw_id(), text)
}
pub fn remove_item(&self, index: usize) -> bool {
crate::list_box_remove_item(self.raw_id(), index)
}
pub fn clear_items(&self) -> bool {
crate::list_box_clear_items(self.raw_id())
}
pub fn set_current_index(&self, index: usize) -> bool {
crate::list_box_set_current_index(self.raw_id(), index)
}
pub fn current_index(&self) -> Option<usize> {
crate::list_box_current_index(self.raw_id())
}
pub fn item_count(&self) -> usize {
crate::list_box_item_count(self.raw_id())
}
pub fn item_text(&self, index: usize) -> Option<String> {
crate::list_box_item_text(self.raw_id(), index)
}
}
#[derive(Debug, Clone)]
struct SliderState {
value: i32,
min: i32,
max: i32,
step: i32,
orientation: Orientation,
}
impl Default for SliderState {
fn default() -> Self {
Self { value: 50, min: 0, max: 100, step: 1, orientation: Orientation::Horizontal }
}
}
thread_local! {
static SLIDER_STATES: RefCell<HashMap<ObjectId, SliderState>> = RefCell::new(HashMap::new());
}
impl SliderHandle {
pub fn set_value(&self, value: i32) {
SLIDER_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.value = value.clamp(state.min, state.max);
});
}
pub fn value(&self) -> i32 {
SLIDER_STATES.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.value).unwrap_or(50))
}
pub fn set_range(&self, min: i32, max: i32) {
SLIDER_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.min = min;
state.max = max;
state.value = state.value.clamp(state.min, state.max);
});
}
pub fn set_step(&self, step: i32) {
SLIDER_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).step = step;
});
}
pub fn set_orientation(&self, orientation: Orientation) {
SLIDER_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).orientation =
orientation;
});
}
}
#[derive(Debug, Clone)]
struct ProgressBarState {
value: u32,
min: u32,
max: u32,
indeterminate: bool,
}
impl Default for ProgressBarState {
fn default() -> Self {
Self { value: 0, min: 0, max: 100, indeterminate: false }
}
}
thread_local! {
static PROGRESS_BAR_STATES: RefCell<HashMap<ObjectId, ProgressBarState>> = RefCell::new(HashMap::new());
}
impl ProgressBarHandle {
pub fn set_value(&self, value: u32) {
PROGRESS_BAR_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.value = value.clamp(state.min, state.max);
});
}
pub fn value(&self) -> u32 {
PROGRESS_BAR_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.value).unwrap_or(0))
}
pub fn set_min(&self, min: u32) {
PROGRESS_BAR_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.min = min;
state.value = state.value.clamp(state.min, state.max);
});
}
pub fn set_max(&self, max: u32) {
PROGRESS_BAR_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.max = max;
state.value = state.value.clamp(state.min, state.max);
});
}
pub fn set_indeterminate(&self, indeterminate: bool) {
PROGRESS_BAR_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).indeterminate =
indeterminate;
});
}
}
#[derive(Debug, Clone)]
struct CheckBoxState {
checked: bool,
tristate: bool,
check_state: CheckState,
}
impl Default for CheckBoxState {
fn default() -> Self {
Self { checked: false, tristate: false, check_state: CheckState::Unchecked }
}
}
thread_local! {
static CHECKBOX_STATES: RefCell<HashMap<ObjectId, CheckBoxState>> = RefCell::new(HashMap::new());
}
impl CheckBoxHandle {
pub fn is_checked(&self) -> bool {
CHECKBOX_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.checked).unwrap_or(false))
}
pub fn set_checked(&self, checked: bool) {
CHECKBOX_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.checked = checked;
if !state.tristate {
state.check_state =
if checked { CheckState::Checked } else { CheckState::Unchecked };
}
});
}
pub fn set_tristate(&self, tristate: bool) {
CHECKBOX_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).tristate =
tristate;
});
}
pub fn check_state(&self) -> CheckState {
CHECKBOX_STATES.with(|map| {
map.borrow().get(&self.raw_id()).map(|s| s.check_state).unwrap_or(CheckState::Unchecked)
})
}
}
#[derive(Debug, Clone, Default)]
struct RadioButtonState {
selected: bool,
group: String,
}
thread_local! {
static RADIO_BUTTON_STATES: RefCell<HashMap<ObjectId, RadioButtonState>> = RefCell::new(HashMap::new());
}
impl RadioButtonHandle {
pub fn is_selected(&self) -> bool {
RADIO_BUTTON_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.selected).unwrap_or(false))
}
pub fn select(&self) {
let group = RADIO_BUTTON_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.selected = true;
state.group.clone()
});
if !group.is_empty() {
RADIO_BUTTON_STATES.with(|map| {
let mut map = map.borrow_mut();
for (id, state) in map.iter_mut() {
if *id != self.raw_id() && state.group == group {
state.selected = false;
}
}
});
}
}
pub fn set_group(&self, group: &str) {
RADIO_BUTTON_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).group =
group.to_owned();
});
}
}
#[derive(Debug, Clone)]
struct LineEditState {
placeholder: String,
read_only: bool,
max_length: u32,
echo_mode: EchoMode,
selection_start: u32,
selection_end: u32,
select_all: bool,
}
impl Default for LineEditState {
fn default() -> Self {
Self {
placeholder: String::new(),
read_only: false,
max_length: 32767,
echo_mode: EchoMode::Normal,
selection_start: 0,
selection_end: 0,
select_all: false,
}
}
}
thread_local! {
static LINE_EDIT_STATES: RefCell<HashMap<ObjectId, LineEditState>> = RefCell::new(HashMap::new());
}
impl LineEditHandle {
pub fn set_placeholder(&self, text: &str) {
LINE_EDIT_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).placeholder =
text.to_owned();
});
}
pub fn set_read_only(&self, read_only: bool) {
LINE_EDIT_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).read_only =
read_only;
});
}
pub fn set_max_length(&self, len: u32) {
LINE_EDIT_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).max_length = len;
});
}
pub fn clear(&self) {
crate::set_widget_text(self.raw_id(), "");
}
pub fn set_echo_mode(&self, mode: EchoMode) {
LINE_EDIT_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).echo_mode = mode;
});
}
pub fn select_all(&self) {
LINE_EDIT_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.select_all = true;
state.selection_start = 0;
state.selection_end = u32::MAX;
});
}
pub fn set_selection(&self, start: u32, end: u32) {
LINE_EDIT_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.select_all = false;
state.selection_start = start;
state.selection_end = end;
});
}
}
#[derive(Debug, Clone, Default)]
struct ScrollAreaState {
scroll_x: i32,
scroll_y: i32,
content_w: u32,
content_h: u32,
}
thread_local! {
static SCROLL_AREA_STATES: RefCell<HashMap<ObjectId, ScrollAreaState>> = RefCell::new(HashMap::new());
}
impl ScrollAreaHandle {
pub fn set_scroll_position(&self, x: i32, y: i32) {
SCROLL_AREA_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.scroll_x = x;
state.scroll_y = y;
});
}
pub fn scroll_position(&self) -> (i32, i32) {
SCROLL_AREA_STATES.with(|map| {
let map = map.borrow();
map.get(&self.raw_id()).map(|s| (s.scroll_x, s.scroll_y)).unwrap_or((0, 0))
})
}
pub fn set_content_size(&self, w: u32, h: u32) {
SCROLL_AREA_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.content_w = w;
state.content_h = h;
});
}
pub fn scroll_to_bottom(&self) {
SCROLL_AREA_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.get(&self.raw_id()).cloned().unwrap_or_default();
let state_mut = map.entry(self.raw_id()).or_default();
state_mut.scroll_y = state.content_h as i32;
});
}
pub fn scroll_to_top(&self) {
SCROLL_AREA_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).scroll_y = 0;
});
}
}
struct ListViewState {
columns: Vec<(String, u32)>,
model: Option<Rc<dyn ListModel>>,
selected_row: Option<usize>,
selection_mode: SelectionMode,
}
impl Default for ListViewState {
fn default() -> Self {
Self {
columns: Vec::new(),
model: None,
selected_row: None,
selection_mode: SelectionMode::Single,
}
}
}
thread_local! {
static LIST_VIEW_STATES: RefCell<HashMap<ObjectId, ListViewState>> = RefCell::new(HashMap::new());
}
impl ListViewHandle {
pub fn add_column(&self, title: &str, width: u32) {
LIST_VIEW_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.columns.push((title.to_owned(), width));
});
}
pub fn set_model(&self, model: Box<dyn ListModel>) {
let model: Rc<dyn ListModel> = Rc::from(model);
LIST_VIEW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).model =
Some(model);
});
}
pub fn selected_row(&self) -> Option<usize> {
LIST_VIEW_STATES.with(|map| map.borrow().get(&self.raw_id()).and_then(|s| s.selected_row))
}
pub fn set_selection_mode(&self, mode: SelectionMode) {
LIST_VIEW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).selection_mode =
mode;
});
}
pub fn model(&self) -> Option<Rc<dyn ListModel>> {
LIST_VIEW_STATES.with(|map| map.borrow().get(&self.raw_id()).and_then(|s| s.model.clone()))
}
}
#[derive(Debug, Clone)]
struct SpinBoxState {
value: i32,
min: i32,
max: i32,
step: i32,
prefix: String,
suffix: String,
}
impl Default for SpinBoxState {
fn default() -> Self {
Self { value: 0, min: 0, max: 100, step: 1, prefix: String::new(), suffix: String::new() }
}
}
thread_local! {
static SPINBOX_STATES: RefCell<HashMap<ObjectId, SpinBoxState>> = RefCell::new(HashMap::new());
}
impl SpinBoxHandle {
pub fn set_value(&self, value: i32) {
SPINBOX_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.value = value.clamp(state.min, state.max);
});
}
pub fn value(&self) -> i32 {
SPINBOX_STATES.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.value).unwrap_or(0))
}
pub fn set_range(&self, min: i32, max: i32) {
SPINBOX_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.min = min;
state.max = max;
state.value = state.value.clamp(state.min, state.max);
});
}
pub fn set_prefix(&self, prefix: &str) {
SPINBOX_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).prefix =
prefix.to_owned();
});
}
pub fn set_suffix(&self, suffix: &str) {
SPINBOX_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).suffix =
suffix.to_owned();
});
}
pub fn set_step(&self, step: i32) {
SPINBOX_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).step = step;
});
}
}
#[derive(Debug, Clone, Default)]
struct PanelState {
title: String,
}
thread_local! {
static PANEL_LAYOUTS: RefCell<HashMap<ObjectId, Box<dyn crate::layout::Layout>>> = RefCell::new(HashMap::new());
static PANEL_STATES: RefCell<HashMap<ObjectId, PanelState>> = RefCell::new(HashMap::new());
}
impl PanelHandle {
pub fn set_layout(&self, layout: Box<dyn crate::layout::Layout>) {
PANEL_LAYOUTS.with(|map| {
map.borrow_mut().insert(self.raw_id(), layout);
});
}
pub fn set_title(&self, title: &str) {
PANEL_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).title =
title.to_owned();
});
crate::set_widget_text(self.raw_id(), title);
}
}
#[derive(Clone)]
struct WindowState {
icon: String,
min_w: u32,
min_h: u32,
maximized: bool,
minimized: bool,
fullscreen: bool,
resizable: bool,
decorated: bool,
close_callback: Option<ClickCallback>,
}
impl std::fmt::Debug for WindowState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowState")
.field("icon", &self.icon)
.field("min_w", &self.min_w)
.field("min_h", &self.min_h)
.field("maximized", &self.maximized)
.field("minimized", &self.minimized)
.field("fullscreen", &self.fullscreen)
.field("resizable", &self.resizable)
.field("decorated", &self.decorated)
.field("close_callback", &self.close_callback.as_ref().map(|_| "<fn>"))
.finish()
}
}
impl Default for WindowState {
fn default() -> Self {
Self {
icon: String::new(),
min_w: 0,
min_h: 0,
maximized: false,
minimized: false,
fullscreen: false,
resizable: true,
decorated: true,
close_callback: None,
}
}
}
thread_local! {
static WINDOW_STATES: RefCell<HashMap<ObjectId, WindowState>> = RefCell::new(HashMap::new());
}
impl WindowHandle {
pub fn title(&self) -> String {
crate::get_widget_text(self.raw_id())
}
pub fn set_icon(&self, path: &str) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).icon =
path.to_owned();
});
}
pub fn set_min_size(&self, w: u32, h: u32) {
WINDOW_STATES.with(|map| {
let mut map = map.borrow_mut();
let state = map.entry(self.raw_id()).or_default();
state.min_w = w;
state.min_h = h;
});
}
pub fn set_maximized(&self, maximized: bool) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).maximized =
maximized;
});
}
pub fn is_maximized(&self) -> bool {
WINDOW_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.maximized).unwrap_or(false))
}
pub fn set_minimized(&self, minimized: bool) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).minimized =
minimized;
});
}
pub fn is_minimized(&self) -> bool {
WINDOW_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.minimized).unwrap_or(false))
}
pub fn set_fullscreen(&self, fullscreen: bool) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).fullscreen =
fullscreen;
});
}
pub fn is_fullscreen(&self) -> bool {
WINDOW_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.fullscreen).unwrap_or(false))
}
pub fn set_resizable(&self, resizable: bool) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).resizable =
resizable;
});
}
pub fn is_resizable(&self) -> bool {
WINDOW_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.resizable).unwrap_or(true))
}
pub fn set_decorated(&self, decorated: bool) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).decorated =
decorated;
});
}
pub fn is_decorated(&self) -> bool {
WINDOW_STATES
.with(|map| map.borrow().get(&self.raw_id()).map(|s| s.decorated).unwrap_or(true))
}
pub fn on_close(&self, callback: ClickCallback) {
WINDOW_STATES.with(|map| {
map.borrow_mut().entry(self.raw_id()).or_insert_with(Default::default).close_callback =
Some(callback);
});
}
pub fn close(&self) {
WINDOW_STATES.with(|map| {
let mut map = map.borrow_mut();
if let Some(state) = map.get_mut(&self.raw_id()) {
if let Some(cb) = &state.close_callback {
(cb.borrow_mut())();
}
}
});
crate::hide_widget(self.raw_id());
}
pub fn center_on_screen(&self) {
let screen_w = 1920i32;
let screen_h = 1080i32;
crate::set_widget_geometry(self.raw_id(), screen_w / 4, screen_h / 4, 640, 480);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ObjectId;
use alloc::rc::Rc;
use core::cell::RefCell;
#[test]
fn remove_callbacks_cleans_up() {
let id: ObjectId = 42;
CLICK_CALLBACKS.with(|map| {
map.borrow_mut().insert(id, Rc::new(RefCell::new(|| {})));
assert!(map.borrow().contains_key(&id));
});
remove_callbacks(id);
CLICK_CALLBACKS.with(|map| {
assert!(!map.borrow().contains_key(&id));
});
}
}