azul_widgets/
table_view.rs1use std::{ops::Range, collections::BTreeMap};
4use azul_core::{
5 dom::{Dom, On, NodeData, DomString, NodeType},
6 callbacks::{
7 Ref, Callback, CallbackInfo, CallbackReturn,
8 IFrameCallbackInfo, IFrameCallbackReturn, DontRedraw,
9 },
10};
11
12#[derive(Debug, Clone)]
13pub struct TableView {
14 pub state: Ref<TableViewState>,
15 pub on_mouse_up: Callback,
16}
17
18impl Default for TableView {
19 fn default() -> Self {
20 Self {
21 state: Ref::default(),
22 on_mouse_up: Callback(Self::default_on_mouse_up),
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct TableViewState {
29 pub work_sheet: BTreeMap<usize, BTreeMap<usize, String>>,
30 pub column_width: f32,
31 pub row_height: f32,
32 pub selected_cell: Option<(usize, usize)>,
33}
34
35impl Default for TableViewState {
36 fn default() -> Self {
37 Self {
38 work_sheet: BTreeMap::default(),
39 column_width: 100.0,
40 row_height: 20.0,
41 selected_cell: None,
42 }
43 }
44}
45
46impl TableViewState {
47
48 pub fn render(&self, rows: Range<usize>, columns: Range<usize>) -> Dom {
51
52 Dom::div()
64 .with_class("__azul-native-table-container")
65 .with_child(
66 Dom::div()
67 .with_class("__azul-native-table-row-number-wrapper")
68 .with_child(
69 Dom::div()
71 .with_class("__azul-native-table-top-left-rect")
72 )
73 .with_child(
74 (rows.start..rows.end.saturating_sub(1))
76 .map(|row_idx|
77 NodeData::label(format!("{}", row_idx + 1))
78 .with_classes(vec![DomString::Static("__azul-native-table-row")])
79 )
80 .collect::<Dom>()
81 .with_class("__azul-native-table-row-numbers")
82 )
83 )
84 .with_child(
85 columns
86 .map(|col_idx|
87 Dom::new(NodeType::Div)
89 .with_class("__azul-native-table-column")
90 .with_child(Dom::label(column_name_from_number(col_idx)).with_class("__azul-native-table-column-name"))
91 .with_child(
92 (rows.start..rows.end)
94 .map(|row_idx|
95 NodeData::new(
96 if let Some(data) = self.work_sheet.get(&col_idx).and_then(|col| col.get(&row_idx)) {
97 NodeType::Label(DomString::Heap(data.clone()))
98 } else {
99 NodeType::Div
100 }
101 ).with_classes(vec![DomString::Static("__azul-native-table-cell")])
102 )
103 .collect::<Dom>()
104 .with_class("__azul-native-table-rows")
105 )
106 )
107 .collect::<Dom>()
108 .with_class("__azul-native-table-column-container")
109 .with_child(
111 Dom::div()
112 .with_class("__azul-native-table-selection")
113 .with_child(Dom::div().with_class("__azul-native-table-selection-handle"))
114 )
115 )
116 }
117
118 pub fn set_cell<I: Into<String>>(&mut self, x: usize, y: usize, value: I) {
119 self.work_sheet
120 .entry(x)
121 .or_insert_with(|| BTreeMap::new())
122 .insert(y, value.into());
123 }
124}
125
126impl TableView {
127
128 #[inline]
129 pub fn new(state: Ref<TableViewState>) -> Self {
130 Self { state, .. Default::default() }
131 }
132
133 #[inline]
134 pub fn with_state(self, state: Ref<TableViewState>) -> Self {
135 Self { state, .. self }
136 }
137
138 #[inline]
139 pub fn on_mouse_up(self, cb: Callback) -> Self {
140 Self { on_mouse_up: cb, .. self }
141 }
142
143 #[inline]
144 pub fn dom(self) -> Dom {
145 let upcasted_table_view = self.state.upcast();
146 Dom::iframe(Self::render_table_iframe_contents, upcasted_table_view.clone())
147 .with_class("__azul-native-table-iframe")
148 .with_callback(On::MouseUp, self.on_mouse_up.0, upcasted_table_view)
149 }
150
151 pub fn default_on_mouse_up(_info: CallbackInfo) -> CallbackReturn {
152 println!("table was clicked");
153 DontRedraw
154 }
155
156 fn render_table_iframe_contents(info: IFrameCallbackInfo) -> IFrameCallbackReturn {
157 let table_view_state = info.state.downcast::<TableViewState>()?;
158 let table_view_state = table_view_state.borrow();
159 let logical_size = info.bounds.get_logical_size();
160 let necessary_rows = (logical_size.height as f32 / table_view_state.row_height).ceil() as usize;
161 let necessary_columns = (logical_size.width as f32 / table_view_state.column_width).ceil() as usize;
162 Some(table_view_state.render(0..necessary_rows, 0..necessary_columns))
163 }
164}
165
166impl Into<Dom> for TableView {
167 fn into(self) -> Dom {
168 self.dom()
169 }
170}
171
172pub fn column_name_from_number(num: usize) -> String {
186 const ALPHABET_LEN: usize = 26;
187 const MAX_LEN: usize = 15;
189
190 #[inline(always)]
191 fn u8_to_char(input: u8) -> u8 {
192 'A' as u8 + input
193 }
194
195 let mut result = [0;MAX_LEN + 1];
196 let mut multiple_of_alphabet = num / ALPHABET_LEN;
197 let mut character_count = 0;
198
199 while multiple_of_alphabet != 0 && character_count < MAX_LEN {
200 let remainder = (multiple_of_alphabet - 1) % ALPHABET_LEN;
201 result[(MAX_LEN - 1) - character_count] = u8_to_char(remainder as u8);
202 character_count += 1;
203 multiple_of_alphabet = (multiple_of_alphabet - 1) / ALPHABET_LEN;
204 }
205
206 result[MAX_LEN] = u8_to_char((num % ALPHABET_LEN) as u8);
207 let zeroed_characters = MAX_LEN.saturating_sub(character_count);
208 let slice = &result[zeroed_characters..];
209 unsafe { ::std::str::from_utf8_unchecked(slice) }.to_string()
210}
211
212#[test]
213fn test_column_name_from_number() {
214 assert_eq!(column_name_from_number(0), String::from("A"));
215 assert_eq!(column_name_from_number(1), String::from("B"));
216 assert_eq!(column_name_from_number(6), String::from("G"));
217 assert_eq!(column_name_from_number(26), String::from("AA"));
218 assert_eq!(column_name_from_number(27), String::from("AB"));
219 assert_eq!(column_name_from_number(225), String::from("HR"));
220}