From be4fe2af61aa4a7740bc7e1680da6479aef1f0ae Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Thu, 15 Apr 2021 20:28:00 -0700 Subject: [PATCH 1/9] Initial Tables API A naive and incomplete binding to the ImGui Tables API. Signed-off-by: Chris Frantz --- imgui/src/lib.rs | 2 + imgui/src/tables.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 imgui/src/tables.rs diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 5d15018..657d4f4 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -31,6 +31,7 @@ pub use self::render::renderer::*; pub use self::stacks::*; pub use self::string::*; pub use self::style::*; +pub use self::tables::*; pub use self::utils::*; pub use self::widget::color_editors::*; pub use self::widget::combo_box::*; @@ -73,6 +74,7 @@ mod popups; mod render; mod stacks; mod style; +mod tables; #[cfg(test)] mod test; mod utils; diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs new file mode 100644 index 0000000..0a16131 --- /dev/null +++ b/imgui/src/tables.rs @@ -0,0 +1,265 @@ +use bitflags::bitflags; +use std::convert::From; + +use crate::sys; +use crate::{Id, ImColor32, ImStr, Ui}; + +bitflags! { + /// Item hover check option flags + #[repr(transparent)] + pub struct TableFlags: u32 { + const NONE = sys::ImGuiTableFlags_None; + const RESIZABLE = sys::ImGuiTableFlags_Resizable; + const REORDERABLE =sys::ImGuiTableFlags_Reorderable; + const HIDEABLE = sys::ImGuiTableFlags_Hideable; + const SORTABLE = sys::ImGuiTableFlags_Sortable; + const NO_SAVED_SETTINGS = sys::ImGuiTableFlags_NoSavedSettings; + const CONTEXT_MENU_IN_BODY = sys::ImGuiTableFlags_ContextMenuInBody; + // Decorations + const ROW_BG = sys::ImGuiTableFlags_RowBg; + const BORDERS_INNER_H = sys::ImGuiTableFlags_BordersInnerH; + const BORDERS_OUTER_H = sys::ImGuiTableFlags_BordersOuterH; + const BORDERS_INNER_V = sys::ImGuiTableFlags_BordersInnerV; + const BORDERS_OUTER_V = sys::ImGuiTableFlags_BordersOuterV; + const BORDERS_H = sys::ImGuiTableFlags_BordersH; + const BORDERS_V = sys::ImGuiTableFlags_BordersV; + const BORDERS_INNER = sys::ImGuiTableFlags_BordersInner; + const BORDERS_OUTER = sys::ImGuiTableFlags_BordersOuter; + const BORDERS= sys::ImGuiTableFlags_Borders; + const NO_BORDERS_IN_BODY = sys::ImGuiTableFlags_NoBordersInBody; + const NO_BORDERS_IN_BODY_UNTIL_RESIZE = sys::ImGuiTableFlags_NoBordersInBodyUntilResize; + // Sizing Policy (read above for defaults) + const SIZING_FIXED_FIT = sys::ImGuiTableFlags_SizingFixedFit; + const SIZING_FIXED_SAME = sys::ImGuiTableFlags_SizingFixedSame; + const SIZING_STRETCH_PROP = sys::ImGuiTableFlags_SizingStretchProp; + const SIZING_STRETCH_SAME = sys::ImGuiTableFlags_SizingStretchSame; + // Sizing Extra Options + const NO_HOST_EXTEND_X = sys::ImGuiTableFlags_NoHostExtendX; + const NO_HOST_EXTEND_Y = sys::ImGuiTableFlags_NoHostExtendY; + const NO_KEEP_COLUMNS_VISIBLE = sys::ImGuiTableFlags_NoKeepColumnsVisible; + const PRECISE_WIDTHS = sys::ImGuiTableFlags_PreciseWidths; + // Clipping + const NO_CLIP = sys::ImGuiTableFlags_NoClip; + // Padding + const PAD_OUTER_X = sys::ImGuiTableFlags_PadOuterX; + const NO_PAD_OUTER_X = sys::ImGuiTableFlags_NoPadOuterX; + const NO_PAD_INNER_X = sys::ImGuiTableFlags_NoPadInnerX; + // Scrolling + const SCROLL_X = sys::ImGuiTableFlags_ScrollX; + const SCROLL_Y = sys::ImGuiTableFlags_ScrollY; + // Sorting + const SORT_MULTI = sys::ImGuiTableFlags_SortMulti; + const SORT_TRISTATE = sys::ImGuiTableFlags_SortTristate; + } +} + +bitflags! { + #[repr(transparent)] + pub struct TableRowFlags: u32 { + const NONE = sys::ImGuiTableRowFlags_None; + const HEADERS = sys::ImGuiTableRowFlags_Headers; + } +} + +bitflags! { + #[repr(transparent)] + pub struct TableColumnFlags: u32 { + // Input configuration flags + const NONE = sys::ImGuiTableColumnFlags_None; + const DEFAULT_HIDE =sys::ImGuiTableColumnFlags_DefaultHide; + const DEFAULT_SORT =sys::ImGuiTableColumnFlags_DefaultSort; + const WIDTH_STRETCH=sys::ImGuiTableColumnFlags_WidthStretch; + const WIDTH_FIXED = sys::ImGuiTableColumnFlags_WidthFixed; + const NO_RESIZE = sys::ImGuiTableColumnFlags_NoResize; + const NO_REORDER = sys::ImGuiTableColumnFlags_NoReorder; + const NO_HIDE = sys::ImGuiTableColumnFlags_NoHide; + const NO_CLIP = sys::ImGuiTableColumnFlags_NoClip; + const NO_SORT = sys::ImGuiTableColumnFlags_NoSort; + const NO_SORT_ASCENDING = sys::ImGuiTableColumnFlags_NoSortAscending; + const NO_SORT_DESCENDING = sys::ImGuiTableColumnFlags_NoSortDescending; + const NO_HEADER_WIDTH = sys::ImGuiTableColumnFlags_NoHeaderWidth; + const PREFER_SORT_ASCENDING = sys::ImGuiTableColumnFlags_PreferSortAscending; + const PREFER_SORT_DESCENDING = sys::ImGuiTableColumnFlags_PreferSortDescending; + const INDENT_ENABLE = sys::ImGuiTableColumnFlags_IndentEnable; + const INDENT_DISABLE = sys::ImGuiTableColumnFlags_IndentDisable; + // Output status flags, read-only via TableGetColumnFlags() + const IS_ENABLED = sys::ImGuiTableColumnFlags_IsEnabled; + const IS_VISIBLE = sys::ImGuiTableColumnFlags_IsVisible; + const IS_SORTED = sys::ImGuiTableColumnFlags_IsSorted; + const IS_HOVERED = sys::ImGuiTableColumnFlags_IsHovered; + } +} + +bitflags! { + #[repr(transparent)] + pub struct TableBgTarget: u32 { + const NONE = sys::ImGuiTableBgTarget_None; + const ROW_BG0 = sys::ImGuiTableBgTarget_RowBg0; + const ROW_BG1 = sys::ImGuiTableBgTarget_RowBg1; + const CELL_BG = sys::ImGuiTableBgTarget_CellBg; + } +} + +impl<'ui> Ui<'ui> { + pub fn begin_table<'p>(&self, str_id: &'p ImStr, column: i32) -> bool { + self.begin_table_with_flags(str_id, column, TableFlags::NONE) + } + pub fn begin_table_with_flags<'p>( + &self, + str_id: &'p ImStr, + column: i32, + flags: TableFlags, + ) -> bool { + self.begin_table_with_outer_size(str_id, column, flags, [0.0, 0.0]) + } + pub fn begin_table_with_outer_size<'p>( + &self, + str_id: &'p ImStr, + column: i32, + flags: TableFlags, + outer_size: [f32; 2], + ) -> bool { + self.begin_table_with_inner_width(str_id, column, flags, outer_size, 0.0) + } + pub fn begin_table_with_inner_width<'p>( + &self, + str_id: &'p ImStr, + column: i32, + flags: TableFlags, + outer_size: [f32; 2], + inner_width: f32, + ) -> bool { + unsafe { + sys::igBeginTable( + str_id.as_ptr(), + column, + flags.bits() as i32, + outer_size.into(), + inner_width, + ) + } + } + + pub fn end_table(&self) { + unsafe { + sys::igEndTable(); + } + } + + pub fn table_next_row(&self) { + self.table_next_row_with_flags(TableRowFlags::NONE); + } + pub fn table_next_row_with_flags(&self, flags: TableRowFlags) { + self.table_next_row_with_height(flags, 0.0); + } + pub fn table_next_row_with_height(&self, flags: TableRowFlags, min_row_height: f32) { + unsafe { + sys::igTableNextRow(flags.bits() as i32, min_row_height); + } + } + + pub fn table_next_column(&self) -> bool { + unsafe { sys::igTableNextColumn() } + } + pub fn table_set_column_index(&self, column_n: i32) -> bool { + unsafe { sys::igTableSetColumnIndex(column_n) } + } + + pub fn table_setup_column<'p>(&self, str_id: &'p ImStr) { + self.table_setup_column_with_flags(str_id, TableColumnFlags::NONE) + } + pub fn table_setup_column_with_flags<'p>(&self, str_id: &'p ImStr, flags: TableColumnFlags) { + self.table_setup_column_with_weight(str_id, flags, 0.0) + } + pub fn table_setup_column_with_weight<'p>( + &self, + str_id: &'p ImStr, + flags: TableColumnFlags, + init_width_or_weight: f32, + ) { + self.table_setup_column_with_id(str_id, flags, init_width_or_weight, Id::Int(0)) + } + pub fn table_setup_column_with_id<'p>( + &self, + str_id: &'p ImStr, + flags: TableColumnFlags, + init_width_or_weight: f32, + user_id: Id, + ) { + unsafe { + sys::igTableSetupColumn( + str_id.as_ptr(), + flags.bits() as i32, + init_width_or_weight, + user_id.as_imgui_id(), + ) + } + } + + pub fn table_setup_scroll_freeze(&self, cols: i32, rows: i32) { + unsafe { + sys::igTableSetupScrollFreeze(cols, rows); + } + } + pub fn table_headers_row(&self) { + unsafe { + sys::igTableHeadersRow(); + } + } + pub fn table_header<'p>(&self, label: &'p ImStr) { + unsafe { + sys::igTableHeader(label.as_ptr()); + } + } + + //pub fn table_get_sort_specs(&self) -> &TableSortSpecs { + // unsafe { sys::igTableGetSortSpecs() } + //} + + pub fn table_get_column_count(&self) -> i32 { + unsafe { sys::igTableGetColumnCount() } + } + pub fn table_get_column_index(&self) -> i32 { + unsafe { sys::igTableGetColumnIndex() } + } + pub fn table_get_row_index(&self) -> i32 { + unsafe { sys::igTableGetRowIndex() } + } + + //pub fn table_get_column_name(&self) -> &str { + // self.table_get_column_name_with_column(-1) + //} + //pub fn table_get_column_name_with_column(&self, column_n: i32) -> &str { + // unsafe { sys::igTableGetColumnName(column_n); } + //} + + pub fn table_get_column_flags(&self) -> TableColumnFlags { + self.table_get_column_flags_with_column(-1) + } + pub fn table_get_column_flags_with_column(&self, column_n: i32) -> TableColumnFlags { + unsafe { + TableColumnFlags::from_bits_unchecked(sys::igTableGetColumnFlags(column_n) as u32) + } + } + + pub fn table_set_column_enabled(&self, column_n: i32, v: bool) { + unsafe { + sys::igTableSetColumnEnabled(column_n, v); + } + } + + pub fn table_set_bg_color(&self, target: TableBgTarget, color: [f32; 4]) { + self.table_set_bg_color_with_column(target, color, -1); + } + pub fn table_set_bg_color_with_column( + &self, + target: TableBgTarget, + color: [f32; 4], + column_n: i32, + ) { + let color = ImColor32::from(color); + unsafe { + sys::igTableSetBgColor(target.bits() as i32, color.to_bits(), column_n); + } + } +} From 56dae31b91c14b3af75021b94212422f63d3bd8e Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Thu, 9 Sep 2021 19:37:51 -0700 Subject: [PATCH 2/9] rebased and updated further. No docs yet, and API isn't stable. This would bring our MSRV up to 1.51 (const generics) --- imgui/src/lib.rs | 20 ++++++ imgui/src/tables.rs | 168 ++++++++++++++++++++++++++++---------------- 2 files changed, 128 insertions(+), 60 deletions(-) diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 657d4f4..07cccec 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -290,6 +290,26 @@ impl From<*mut T> for Id<'static> { } } +impl<'a> Id<'a> { + fn as_imgui_id(&self) -> sys::ImGuiID { + unsafe { + match self { + Id::Ptr(p) => sys::igGetIDPtr(*p), + Id::Str(s) => { + let s1 = s.as_ptr() as *const std::os::raw::c_char; + let s2 = s1.add(s.len()); + sys::igGetIDStrStr(s1, s2) + } + Id::Int(i) => { + let p = *i as *const std::os::raw::c_void; + sys::igGetIDPtr(p) + } + // Id::ImGuiID(n) => *n, + } + } + } +} + // Widgets: Input impl<'ui> Ui<'ui> { #[doc(alias = "InputText", alias = "InputTextWithHint")] diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 0a16131..9325426 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -63,6 +63,7 @@ bitflags! { bitflags! { #[repr(transparent)] + #[derive(Default)] pub struct TableColumnFlags: u32 { // Input configuration flags const NONE = sys::ImGuiTableColumnFlags_None; @@ -93,43 +94,34 @@ bitflags! { bitflags! { #[repr(transparent)] pub struct TableBgTarget: u32 { - const NONE = sys::ImGuiTableBgTarget_None; - const ROW_BG0 = sys::ImGuiTableBgTarget_RowBg0; - const ROW_BG1 = sys::ImGuiTableBgTarget_RowBg1; - const CELL_BG = sys::ImGuiTableBgTarget_CellBg; + const NONE = sys::ImGuiTableBgTarget_None; + const ROW_BG0 = sys::ImGuiTableBgTarget_RowBg0; + const ROW_BG1 = sys::ImGuiTableBgTarget_RowBg1; + const CELL_BG = sys::ImGuiTableBgTarget_CellBg; } } impl<'ui> Ui<'ui> { - pub fn begin_table<'p>(&self, str_id: &'p ImStr, column: i32) -> bool { - self.begin_table_with_flags(str_id, column, TableFlags::NONE) + pub fn begin_table(&self, str_id: &ImStr, column_count: i32) -> Option> { + self.begin_table_with_flags(str_id, column_count, TableFlags::NONE) } - pub fn begin_table_with_flags<'p>( + pub fn begin_table_with_flags( &self, - str_id: &'p ImStr, - column: i32, + str_id: &ImStr, + column_count: i32, flags: TableFlags, - ) -> bool { - self.begin_table_with_outer_size(str_id, column, flags, [0.0, 0.0]) + ) -> Option> { + self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0) } - pub fn begin_table_with_outer_size<'p>( + pub fn begin_table_with_sizing( &self, - str_id: &'p ImStr, - column: i32, - flags: TableFlags, - outer_size: [f32; 2], - ) -> bool { - self.begin_table_with_inner_width(str_id, column, flags, outer_size, 0.0) - } - pub fn begin_table_with_inner_width<'p>( - &self, - str_id: &'p ImStr, + str_id: &ImStr, column: i32, flags: TableFlags, outer_size: [f32; 2], inner_width: f32, - ) -> bool { - unsafe { + ) -> Option> { + let should_render = unsafe { sys::igBeginTable( str_id.as_ptr(), column, @@ -137,21 +129,61 @@ impl<'ui> Ui<'ui> { outer_size.into(), inner_width, ) - } + }; + + should_render.then(|| TableToken::new(self)) } - pub fn end_table(&self) { - unsafe { - sys::igEndTable(); - } + pub fn begin_table_header<'a, const N: usize>( + &self, + str_id: &ImStr, + column_data: [TableHeader<'a>; N], + ) -> Option> { + self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE) + } + pub fn begin_table_header_with_flags<'a, const N: usize>( + &self, + str_id: &ImStr, + column_data: [TableHeader<'a>; N], + flags: TableFlags, + ) -> Option> { + self.begin_table_header_with_sizing(str_id, column_data, flags, [0.0, 0.0], 0.0) + } + pub fn begin_table_header_with_sizing<'a, const N: usize>( + &self, + str_id: &ImStr, + column_data: [TableHeader<'a>; N], + flags: TableFlags, + outer_size: [f32; 2], + inner_width: f32, + ) -> Option> { + self.begin_table_with_sizing(str_id, N as i32, flags, outer_size, inner_width) + .map(|data| { + for value in column_data { + self.table_setup_column_with_id( + value.name, + value.flags, + value.init_width_or_weight, + value.user_id, + ); + } + self.table_headers_row(); + + data + }) } + #[inline] pub fn table_next_row(&self) { self.table_next_row_with_flags(TableRowFlags::NONE); } + + #[inline] pub fn table_next_row_with_flags(&self, flags: TableRowFlags) { self.table_next_row_with_height(flags, 0.0); } + + #[inline] pub fn table_next_row_with_height(&self, flags: TableRowFlags, min_row_height: f32) { unsafe { sys::igTableNextRow(flags.bits() as i32, min_row_height); @@ -161,27 +193,20 @@ impl<'ui> Ui<'ui> { pub fn table_next_column(&self) -> bool { unsafe { sys::igTableNextColumn() } } - pub fn table_set_column_index(&self, column_n: i32) -> bool { - unsafe { sys::igTableSetColumnIndex(column_n) } + pub fn table_set_column_index(&self, column_index: usize) -> bool { + unsafe { sys::igTableSetColumnIndex(column_index as i32) } } - pub fn table_setup_column<'p>(&self, str_id: &'p ImStr) { + pub fn table_setup_column(&self, str_id: &ImStr) { self.table_setup_column_with_flags(str_id, TableColumnFlags::NONE) } - pub fn table_setup_column_with_flags<'p>(&self, str_id: &'p ImStr, flags: TableColumnFlags) { - self.table_setup_column_with_weight(str_id, flags, 0.0) + + pub fn table_setup_column_with_flags(&self, str_id: &ImStr, flags: TableColumnFlags) { + self.table_setup_column_with_id(str_id, flags, 0.0, Id::Int(0)) } - pub fn table_setup_column_with_weight<'p>( + pub fn table_setup_column_with_id( &self, - str_id: &'p ImStr, - flags: TableColumnFlags, - init_width_or_weight: f32, - ) { - self.table_setup_column_with_id(str_id, flags, init_width_or_weight, Id::Int(0)) - } - pub fn table_setup_column_with_id<'p>( - &self, - str_id: &'p ImStr, + str_id: &ImStr, flags: TableColumnFlags, init_width_or_weight: f32, user_id: Id, @@ -196,9 +221,9 @@ impl<'ui> Ui<'ui> { } } - pub fn table_setup_scroll_freeze(&self, cols: i32, rows: i32) { + pub fn table_setup_scroll_freeze(&self, locked_columns: i32, locked_rows: i32) { unsafe { - sys::igTableSetupScrollFreeze(cols, rows); + sys::igTableSetupScrollFreeze(locked_columns, locked_rows); } } pub fn table_headers_row(&self) { @@ -206,7 +231,7 @@ impl<'ui> Ui<'ui> { sys::igTableHeadersRow(); } } - pub fn table_header<'p>(&self, label: &'p ImStr) { + pub fn table_header(&self, label: &ImStr) { unsafe { sys::igTableHeader(label.as_ptr()); } @@ -226,13 +251,6 @@ impl<'ui> Ui<'ui> { unsafe { sys::igTableGetRowIndex() } } - //pub fn table_get_column_name(&self) -> &str { - // self.table_get_column_name_with_column(-1) - //} - //pub fn table_get_column_name_with_column(&self, column_n: i32) -> &str { - // unsafe { sys::igTableGetColumnName(column_n); } - //} - pub fn table_get_column_flags(&self) -> TableColumnFlags { self.table_get_column_flags_with_column(-1) } @@ -242,12 +260,6 @@ impl<'ui> Ui<'ui> { } } - pub fn table_set_column_enabled(&self, column_n: i32, v: bool) { - unsafe { - sys::igTableSetColumnEnabled(column_n, v); - } - } - pub fn table_set_bg_color(&self, target: TableBgTarget, color: [f32; 4]) { self.table_set_bg_color_with_column(target, color, -1); } @@ -263,3 +275,39 @@ impl<'ui> Ui<'ui> { } } } + +pub struct TableHeader<'a> { + pub name: &'a ImStr, + pub flags: TableColumnFlags, + pub init_width_or_weight: f32, + pub user_id: Id<'a>, +} + +impl<'a> TableHeader<'a> { + pub fn new(name: &'a ImStr) -> Self { + Self { + name, + ..Default::default() + } + } +} + +impl<'a> Default for TableHeader<'a> { + fn default() -> Self { + Self { + name: Default::default(), + flags: Default::default(), + init_width_or_weight: Default::default(), + user_id: Id::Int(0), + } + } +} + +create_token!( + /// Tracks a table which can be rendered onto, ending with `.end()` + /// or by dropping. + pub struct TableToken<'ui>; + + /// Pops a change from the font stack. + drop { sys::igEndTable() } +); From 7aba745c3d594fa3c1ec42f67bf1b21f66231ffb Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Fri, 10 Sep 2021 17:55:47 -0700 Subject: [PATCH 3/9] added documentation and examples --- imgui-examples/examples/tables_api.rs | 134 +++++++ imgui/src/tables.rs | 514 ++++++++++++++++++++++---- 2 files changed, 584 insertions(+), 64 deletions(-) create mode 100644 imgui-examples/examples/tables_api.rs diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs new file mode 100644 index 0000000..36865eb --- /dev/null +++ b/imgui-examples/examples/tables_api.rs @@ -0,0 +1,134 @@ +use imgui::*; + +mod support; + +fn main() { + let system = support::init(file!()); + + let mut t2_flags = TableFlags::REORDERABLE + | TableFlags::HIDEABLE + | TableFlags::RESIZABLE + | TableFlags::NO_BORDERS_IN_BODY; + + system.main_loop(move |_, ui| { + Window::new(im_str!("Input text callbacks")) + .size([800.0, 400.0], Condition::FirstUseEver) + .build(ui, || { + if let Some(_t) = ui.begin_table(im_str!("Basic-Table"), 3) { + // we must also call `next_row` here, because we declined + // to set up header rows. If we set up header rows ourselves, + // we will call `table_header_rows` instead, and if we use + // `begin_table_header`, then the initial call will be handled for us. + + ui.table_next_row(); + + // note you MUST call `next_column` at least to START + // Let's walk through a table like it's an iterator... + ui.table_set_column_index(0); + ui.text("x: 0, y: 0"); + + ui.table_next_column(); + ui.text("x: 1, y: 0"); + + ui.table_next_column(); + ui.text("x: 2, y: 0"); + + // // calling next column again will wrap us around to 0-1, + // // since we've exhausted our 3 columns. + ui.table_next_column(); + ui.text("x: 0, y: 1"); + + // // Let's do this manually now -- we can set each column ourselves... + ui.table_set_column_index(1); + ui.text("x: 1, y: 1"); + + ui.table_set_column_index(2); + ui.text("x: 2, y: 1"); + + // you CAN go back... + ui.table_set_column_index(1); + // however, you should call `new_line`, since otherwise + // we'd right on top of our `x: 1, y: 1` text. + ui.new_line(); + ui.text("our of order txt"); + } + + ui.separator(); + ui.text("Let's add some headers"); + if let Some(_t) = ui.begin_table_header( + im_str!("table-headers"), + [ + TableColumnSetup::new(im_str!("Name")), + TableColumnSetup::new(im_str!("Age")), + TableColumnSetup::new(im_str!("Favorite fruit")), + ], + ) { + // note that we DON'T have to call "table_next_row" here -- that's taken care + // of for us by `begin_table_header`, since it actually calls `table_headers_row` + + // but we DO need to call column! + // but that's fine, we'll use a loop + for i in 0..3 { + let names = ["Joonas", "Thom", "Jack"]; + let fruit = ["Dutch", "Rice", "Mangoes"]; + + ui.table_next_column(); + ui.text(names[i]); + + ui.table_next_column(); + ui.text((i * 9).to_string()); + + ui.table_next_column(); + ui.text(fruit[i]); + } + } + + ui.separator(); + ui.text("Let's do some context menus"); + ui.text( + "context menus are created, by default, from the flags passed\ + while making the table, or each row.\n\ + Notice how toggling these checkboxes changes the context menu.", + ); + + ui.checkbox_flags( + im_str!("Reorderable"), + &mut t2_flags, + TableFlags::REORDERABLE, + ); + ui.same_line(); + ui.checkbox_flags(im_str!("Hideable"), &mut t2_flags, TableFlags::HIDEABLE); + ui.same_line(); + ui.checkbox_flags(im_str!("Resizable"), &mut t2_flags, TableFlags::RESIZABLE); + + if let Some(_t) = ui.begin_table_header_with_flags( + im_str!("table-headers2"), + [ + TableColumnSetup::new(im_str!("Name")), + TableColumnSetup::new(im_str!("Age")), + TableColumnSetup::new(im_str!("Favorite fruit")), + ], + t2_flags, + ) { + // note that we DON'T have to call "table_next_row" here -- that's taken care + // of for us by `begin_table_header`, since it actually calls `table_headers_row` + + // but we DO need to call column! + // but that's fine, we'll use a loop + for i in 0..3 { + let names = ["Joonas", "Thom", "Jack"]; + let fruit = ["Dutch", "Rice", "Mangoes"]; + + ui.table_next_column(); + ui.text(names[i]); + + ui.table_next_column(); + ui.text((i * 9).to_string()); + + ui.table_next_column(); + ui.text(fruit[i]); + } + } + }); + }); +} diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 9325426..3eb235b 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -1,110 +1,265 @@ use bitflags::bitflags; -use std::convert::From; use crate::sys; use crate::{Id, ImColor32, ImStr, Ui}; bitflags! { - /// Item hover check option flags + /// Flags passed to `begin_table` methods. + /// + /// Important! Sizing policies have complex and subtle side effects, more so than you would expect. + /// Read comments/demos carefully + experiment with live demos to get acquainted with them. + /// - The DEFAULT sizing policies are: + /// - Default to [SizingFixedFit] if [ScrollX] is on, or if host window has (WindowFlags::AlwaysAutoResize)[crate::WindowFlags::AlwaysAutoResize]. + /// - Default to [SizingStretchSame] if [ScrollX] is off. + /// - When [ScrollX] is off: + /// - Table defaults to [SizingStretchSame] -> all Columns defaults to [TableColumnFlags::WidthStretch] with same weight. + /// - Columns sizing policy allowed: [Stretch] (default), [Fixed]/Auto. + /// - [Fixed] Columns will generally obtain their requested width (unless the table cannot fit them all). + /// - [Stretch] Columns will share the remaining width. + /// - Mixed [Fixed]/[Stretch] columns is possible but has various side-effects on resizing behaviors. + /// The typical use of mixing sizing policies is: any number of LEADING [Fixed] columns, followed by one or two TRAILING [Stretch] columns. + /// (this is because the visible order of columns have subtle but necessary effects on how they react to manual resizing). + /// - When [ScrollX] is on: + /// - Table defaults to [SizingFixedFit] -> all Columns defaults to [TableColumnFlags::WidthFixed] + /// - Columns sizing policy allowed: [Fixed]/Auto mostly. + /// - [Fixed] Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. + /// - When using auto-resizing (non-resizable) fixed columns, querying the content width to use item right-alignment e.g. SetNextItemWidth(-FLT_MIN) doesn't make sense, would create a feedback loop. + /// - Using [Stretch] columns OFTEN DOES NOT MAKE SENSE if [ScrollX] is on, UNLESS you have specified a value for `inner_width` in BeginTable(). + /// If you specify a value for `inner_width` then effectively the scrolling space is known and [Stretch] or mixed [Fixed]/[Stretch] columns become meaningful again. + /// - Read on documentation at the top of imgui_tables.cpp for more details. #[repr(transparent)] pub struct TableFlags: u32 { - const NONE = sys::ImGuiTableFlags_None; + // Features + + /// Enable resizing columns. const RESIZABLE = sys::ImGuiTableFlags_Resizable; + /// Enable reordering columns in header row, though you must set up a header row + /// with `begin_table_header` or `table_setup_column`. const REORDERABLE =sys::ImGuiTableFlags_Reorderable; + /// Enable hiding/disabling columns in context menu. const HIDEABLE = sys::ImGuiTableFlags_Hideable; + /// Enable sorting. See `table_get_sort_specs` to object sort specs. Also see [SortMulti] + /// and [SortTristate]. const SORTABLE = sys::ImGuiTableFlags_Sortable; + /// Disable persisting columns order, width, and sort settings in the .ini file. const NO_SAVED_SETTINGS = sys::ImGuiTableFlags_NoSavedSettings; + /// Right-click on columns body/contents will display table context menu. + /// By default you can only right click in a headers row. const CONTEXT_MENU_IN_BODY = sys::ImGuiTableFlags_ContextMenuInBody; + // Decorations + + /// Set each RowBg color with [table_row_bg] or [table_row_bg_alt] (equivalent of calling + /// `table_set_bg_color` with `ROW_BG0` on each row manually) const ROW_BG = sys::ImGuiTableFlags_RowBg; + /// Draw horizontal borders between rows. const BORDERS_INNER_H = sys::ImGuiTableFlags_BordersInnerH; + /// Draw horizontal borders at the top and bottom. const BORDERS_OUTER_H = sys::ImGuiTableFlags_BordersOuterH; + /// Draw vertical borders between columns. const BORDERS_INNER_V = sys::ImGuiTableFlags_BordersInnerV; + /// Draw vertical borders on the left and right sides. const BORDERS_OUTER_V = sys::ImGuiTableFlags_BordersOuterV; + /// Draw all horizontal borders (this is just [BORDERS_INNER_H] | [BORDERS_OUTER_H]). const BORDERS_H = sys::ImGuiTableFlags_BordersH; + /// Draw all vertical borders (this is just [BORDERS_INNER_V] | [BORDERS_OUTER_V]). const BORDERS_V = sys::ImGuiTableFlags_BordersV; + /// Draw all inner borders (this is just [BORDERS_INNER_H] | [BORDERS_INNER_V]). const BORDERS_INNER = sys::ImGuiTableFlags_BordersInner; + /// Draw all outer borders (this is just [BORDERS_OUTER_H] | [BORDERS_OUTER_V]). const BORDERS_OUTER = sys::ImGuiTableFlags_BordersOuter; - const BORDERS= sys::ImGuiTableFlags_Borders; + /// Draw all borders (this is just [BORDERS_INNER] | [BORDERS_OUTER]). + const BORDERS = sys::ImGuiTableFlags_Borders; + /// **ALPHA** Disable vertical borders in columns Body (borders will always appears in Headers). + /// May move to Style const NO_BORDERS_IN_BODY = sys::ImGuiTableFlags_NoBordersInBody; + /// **ALPHA** Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). + /// May move to style const NO_BORDERS_IN_BODY_UNTIL_RESIZE = sys::ImGuiTableFlags_NoBordersInBodyUntilResize; + // Sizing Policy (read above for defaults) + + /// Columns default to [WidthFixed] or [WidthAuto] (if resizable or not resizable), + /// matching contents width. const SIZING_FIXED_FIT = sys::ImGuiTableFlags_SizingFixedFit; + /// Columns default to [WidthFixed] or [WidthAuto] (if resizable or not resizable), + /// matching the maximum contents width of all columns. + /// Implicitly enable [NoKeepColumnsVisible]. const SIZING_FIXED_SAME = sys::ImGuiTableFlags_SizingFixedSame; + /// Columns default to [WidthStretch] with default weights proportional to each columns + /// contents widths. const SIZING_STRETCH_PROP = sys::ImGuiTableFlags_SizingStretchProp; + /// Columns default to [WidthStretch] with default weights all equal, unless overridden by + /// a column's `TableHeader`. const SIZING_STRETCH_SAME = sys::ImGuiTableFlags_SizingStretchSame; + // Sizing Extra Options + + /// Make outer width auto-fit to columns, overriding outer_size.x value. Only available when + /// [ScrollX]/[ScrollY] are disabled and [Stretch] columns are not used. const NO_HOST_EXTEND_X = sys::ImGuiTableFlags_NoHostExtendX; + /// Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). + /// Only available when [ScrollX]/[ScrollY] are disabled. + /// Data below the limit will be clipped and not visible. const NO_HOST_EXTEND_Y = sys::ImGuiTableFlags_NoHostExtendY; + /// Disable keeping column always minimally visible when [ScrollX] is off and table + /// gets too small. Not recommended if columns are resizable. const NO_KEEP_COLUMNS_VISIBLE = sys::ImGuiTableFlags_NoKeepColumnsVisible; + /// Disable distributing remainder width to stretched columns (width allocation on a 100-wide + /// table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). + /// With larger number of columns, resizing will appear to be less smooth. const PRECISE_WIDTHS = sys::ImGuiTableFlags_PreciseWidths; + // Clipping + + /// Disable clipping rectangle for every individual columns (reduce draw command count, items will + /// be able to overflow into other columns). Generally incompatible with [table_setup_scroll_freeze]. const NO_CLIP = sys::ImGuiTableFlags_NoClip; + // Padding + + /// Default if [BordersOuterV] is on. Enable outer-most padding. Generally desirable if you have headers. const PAD_OUTER_X = sys::ImGuiTableFlags_PadOuterX; + /// Default if [BordersOuterV] is off. Disable outer-most padding. const NO_PAD_OUTER_X = sys::ImGuiTableFlags_NoPadOuterX; + /// Disable inner padding between columns (double inner padding if [BordersOuterV] is on, single + /// inner padding if BordersOuterV is off). const NO_PAD_INNER_X = sys::ImGuiTableFlags_NoPadInnerX; + // Scrolling + + /// Enable horizontal scrolling. Require 'outer_size' parameter of [begin_table] to specify the + /// container size. Changes default sizing policy. Because this create a child window, + /// [ScrollY] is currently generally recommended when using [ScrollX]. const SCROLL_X = sys::ImGuiTableFlags_ScrollX; + /// Enable vertical scrolling. Require 'outer_size' parameter of [begin_table] to specify the + /// container size. const SCROLL_Y = sys::ImGuiTableFlags_ScrollY; + // Sorting + + /// Hold shift when clicking headers to sort on multiple column. [table_get_sort_specs] may return specs where `[spec_count] > 1`. const SORT_MULTI = sys::ImGuiTableFlags_SortMulti; + /// Allow no sorting, disable default sorting. `table_get_sort_specs` may return specs where `[specs_count] == 0`. const SORT_TRISTATE = sys::ImGuiTableFlags_SortTristate; } } bitflags! { + /// Flags for [table_next_row_with_flags]. #[repr(transparent)] pub struct TableRowFlags: u32 { - const NONE = sys::ImGuiTableRowFlags_None; + /// Identify header row (set default background color + width of its contents + /// accounted different for auto column width) const HEADERS = sys::ImGuiTableRowFlags_Headers; } } bitflags! { + /// Flags for [TableColumnSetup] and [table_setup_column_with]. #[repr(transparent)] #[derive(Default)] pub struct TableColumnFlags: u32 { // Input configuration flags - const NONE = sys::ImGuiTableColumnFlags_None; - const DEFAULT_HIDE =sys::ImGuiTableColumnFlags_DefaultHide; - const DEFAULT_SORT =sys::ImGuiTableColumnFlags_DefaultSort; - const WIDTH_STRETCH=sys::ImGuiTableColumnFlags_WidthStretch; + + /// Default as a hidden/disabled column. + const DEFAULT_HIDE = sys::ImGuiTableColumnFlags_DefaultHide; + /// Default as a sorting column. + const DEFAULT_SORT = sys::ImGuiTableColumnFlags_DefaultSort; + /// Column will stretch. Preferable with horizontal scrolling disabled (default + /// if table sizing policy is [ImGuiTableFlags::SizingStretchSame] or + /// [ImGuiTableFlags::SizingStretchProp]). + const WIDTH_STRETCH = sys::ImGuiTableColumnFlags_WidthStretch; + /// Column will not stretch. Preferable with horizontal scrolling enabled (default + /// if table sizing policy is [ImGuiTableFlags::SizingFixedFit] and table is resizable). const WIDTH_FIXED = sys::ImGuiTableColumnFlags_WidthFixed; + /// Disable manual resizing. const NO_RESIZE = sys::ImGuiTableColumnFlags_NoResize; + /// Disable manual reordering this column, this will also prevent other columns from + /// crossing over this column. const NO_REORDER = sys::ImGuiTableColumnFlags_NoReorder; + /// Disable ability to hide/disable this column. const NO_HIDE = sys::ImGuiTableColumnFlags_NoHide; + /// Disable clipping for this column (all [NO_CLIP] columns will render in a same + /// draw command). const NO_CLIP = sys::ImGuiTableColumnFlags_NoClip; + /// Disable ability to sort on this field (even if [ImGuiTableFlags::Sortable] is + /// set on the table). const NO_SORT = sys::ImGuiTableColumnFlags_NoSort; + /// Disable ability to sort in the ascending direction. const NO_SORT_ASCENDING = sys::ImGuiTableColumnFlags_NoSortAscending; + /// Disable ability to sort in the descending direction. const NO_SORT_DESCENDING = sys::ImGuiTableColumnFlags_NoSortDescending; + /// Disable header text width contribution to automatic column width. const NO_HEADER_WIDTH = sys::ImGuiTableColumnFlags_NoHeaderWidth; + /// Make the initial sort direction Ascending when first sorting on this column (default). const PREFER_SORT_ASCENDING = sys::ImGuiTableColumnFlags_PreferSortAscending; + /// Make the initial sort direction Descending when first sorting on this column. const PREFER_SORT_DESCENDING = sys::ImGuiTableColumnFlags_PreferSortDescending; + /// Use current Indent value when entering cell (default for column 0). const INDENT_ENABLE = sys::ImGuiTableColumnFlags_IndentEnable; + /// Ignore current Indent value when entering cell (default for columns > 0). + /// Indentation changes _within_ the cell will still be honored. const INDENT_DISABLE = sys::ImGuiTableColumnFlags_IndentDisable; - // Output status flags, read-only via TableGetColumnFlags() + + // Output status flags, read-only via [table_get_column_flags] + + /// Status: is enabled == not hidden by user/api (referred to as "Hide" in + /// [DefaultHide] and [NoHide]) flags. const IS_ENABLED = sys::ImGuiTableColumnFlags_IsEnabled; + /// Status: is visible == is enabled AND not clipped by scrolling. const IS_VISIBLE = sys::ImGuiTableColumnFlags_IsVisible; + /// Status: is currently part of the sort specs const IS_SORTED = sys::ImGuiTableColumnFlags_IsSorted; + /// Status: is hovered by mouse const IS_HOVERED = sys::ImGuiTableColumnFlags_IsHovered; } } bitflags! { + /// Enum for [table_set_bg_color]. + /// Background colors are rendering in 3 layers: + /// - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. + /// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. + /// - Layer 2: draw with CellBg color if set. + /// The purpose of the two row/columns layers is to let you decide if a background color + /// changes should override or blend with the existing color. + /// When using [TableFlags::RowBg] on the table, each row has the RowBg0 color automatically + /// set for odd/even rows. + /// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. + /// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. #[repr(transparent)] pub struct TableBgTarget: u32 { - const NONE = sys::ImGuiTableBgTarget_None; + /// Set row background color 0 (generally used for background, automatically set when + /// [TableFlags::RowBg] is used) const ROW_BG0 = sys::ImGuiTableBgTarget_RowBg0; + /// Set row background color 1 (generally used for selection marking) const ROW_BG1 = sys::ImGuiTableBgTarget_RowBg1; + /// Set cell background color (top-most color) const CELL_BG = sys::ImGuiTableBgTarget_CellBg; } } impl<'ui> Ui<'ui> { + /// Begins a table with no flags and with standard sizing contraints. + /// + /// This does no work on styling the headers (the top row) -- see either + /// [begin_table_with_headers](Self::begin_table_with_headers) or the more complex + /// [table_setup_column](Self::table_setup_column). + /// + /// **NB:** after you begin a table (and after setting up ) + #[inline] + #[must_use = "if return is dropped immediately, table is ended immediately."] pub fn begin_table(&self, str_id: &ImStr, column_count: i32) -> Option> { - self.begin_table_with_flags(str_id, column_count, TableFlags::NONE) + self.begin_table_with_flags(str_id, column_count, TableFlags::empty()) } + + /// Begins a table with flags and standard sizing contraints. + /// + /// This does no work on styling the headers (the top row) -- see either + /// [begin_table_with_headers](Self::begin_table_with_headers) or the more complex + /// [table_setup_column](Self::table_setup_column). + #[inline] pub fn begin_table_with_flags( &self, str_id: &ImStr, @@ -113,6 +268,14 @@ impl<'ui> Ui<'ui> { ) -> Option> { self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0) } + + /// Begins a table with all flags and sizing contraints. This is the base method, + /// and gives users the most flexibility. + /// + /// This does no work on styling the headers (the top row) -- see either + /// [begin_table_header](Self::begin_table_header) or the more complex + /// [table_setup_column](Self::table_setup_column). + #[inline] pub fn begin_table_with_sizing( &self, str_id: &ImStr, @@ -134,25 +297,39 @@ impl<'ui> Ui<'ui> { should_render.then(|| TableToken::new(self)) } + /// Begins a table with no flags and with standard sizing contraints. + /// + /// Takes an array of table header information, the length of which determines + /// how many columns will be created. pub fn begin_table_header<'a, const N: usize>( &self, str_id: &ImStr, - column_data: [TableHeader<'a>; N], + column_data: [TableColumnSetup<'a>; N], ) -> Option> { - self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE) + self.begin_table_header_with_flags(str_id, column_data, TableFlags::empty()) } + + /// Begins a table with flags and standard sizing contraints. + /// + /// Takes an array of table header information, the length of which determines + /// how many columns will be created. pub fn begin_table_header_with_flags<'a, const N: usize>( &self, str_id: &ImStr, - column_data: [TableHeader<'a>; N], + column_data: [TableColumnSetup<'a>; N], flags: TableFlags, ) -> Option> { self.begin_table_header_with_sizing(str_id, column_data, flags, [0.0, 0.0], 0.0) } + + /// Begins a table with all flags and sizing contraints. This is the base method, + /// and gives users the most flexibility. + /// Takes an array of table header information, the length of which determines + /// how many columns will be created. pub fn begin_table_header_with_sizing<'a, const N: usize>( &self, str_id: &ImStr, - column_data: [TableHeader<'a>; N], + column_data: [TableColumnSetup<'a>; N], flags: TableFlags, outer_size: [f32; 2], inner_width: f32, @@ -160,12 +337,7 @@ impl<'ui> Ui<'ui> { self.begin_table_with_sizing(str_id, N as i32, flags, outer_size, inner_width) .map(|data| { for value in column_data { - self.table_setup_column_with_id( - value.name, - value.flags, - value.init_width_or_weight, - value.user_id, - ); + self.table_setup_column_with(value); } self.table_headers_row(); @@ -173,16 +345,39 @@ impl<'ui> Ui<'ui> { }) } + /// Moves a table to the next row (ie, down) with no flags, + /// and with the next row having a standard computed height. + /// + /// If your table was made with [begin_table], this **must** be called + /// before rendering any cells (along with [table_next_column]). + /// If your table was made with [begin_table_header], this does not need to be called, + /// though [table_next_column] still should be. + /// + /// [begin_table]: Self::begin_table + /// [begin_table_header]: Self::begin_table_header + /// [table_next_column]: Self::table_next_column #[inline] pub fn table_next_row(&self) { - self.table_next_row_with_flags(TableRowFlags::NONE); + self.table_next_row_with_flags(TableRowFlags::empty()); } + /// Moves a table to the next row (ie, down), with the given flags, + /// and with the next row having a standard computed height. + /// + /// Setting a flag here will make the next row a "header" now, which may + /// require setup of column data. + /// + /// See [table_next_row] for information on how moving rows work. To set the row + /// with a given height, see [table_next_row_with_height]. #[inline] pub fn table_next_row_with_flags(&self, flags: TableRowFlags) { self.table_next_row_with_height(flags, 0.0); } + /// Moves a table to the next row (ie, down), with the given flags, + /// and with the given minimum height. + /// + /// See [table_next_row] for information on how moving rows work. #[inline] pub fn table_next_row_with_height(&self, flags: TableRowFlags, min_row_height: f32) { unsafe { @@ -190,100 +385,291 @@ impl<'ui> Ui<'ui> { } } + /// Moves onto the next column. If at `column_count`, this will move to the next row. + /// In this way, you can use this function as an iterator over each cell in the table. + /// + /// # Example + /// ```rs + /// ### let ui: Ui<'static> = unimplemented!(); + /// if let Some(_t) = ui.begin_table(im_str!("Basic-Table"), 2) { + /// // we have to call next_row because we didn't make headers.. + /// ui.table_next_row(); + /// + /// // you always have to call this to start... + /// // take advantage of this in loops! + /// ui.table_next_column(); + /// ui.text("x: 0, y: 0"); + /// + /// ui.table_next_column(); + /// ui.text("x: 1, y: 0"); + /// + /// // notice that we go down a row here too. + /// ui.table_next_column(); + /// ui.text("x: 0, y: 1"); + /// + /// ui.table_next_column(); + /// ui.text("x: 1, y: 1"); + /// } + /// ``` + /// + /// This functions returns true if the given column is **visible.** It is not + /// marked as must use, as you can still render commands into the not-visible column, + /// though you can choose to not as an optimization. pub fn table_next_column(&self) -> bool { unsafe { sys::igTableNextColumn() } } + + /// Moves onto the given column. + /// + /// # Example + /// ```rs + /// ### let ui: Ui<'static> = unimplemented!(); + /// if let Some(_t) = ui.begin_table(im_str!("Basic-Table"), 2) { + /// // we have to call next_row because we didn't make headers.. + /// ui.table_next_row(); + /// + /// for i in 0..2 { + /// ui.table_set_column_index(i); + /// ui.text(format!("x: {}", i)); + /// } + /// + /// // oops I just remembered, i need to add something on idx 0! + /// ui.table_set_column_index(0); + /// // if i uncomment this line, we'll write on top of our previous "x: 0" + /// // line: + /// // ui.text("hello from the future on top of the past"); + /// // so we do a .newline(); + /// ui.new_line(); + /// ui.text("hello from the future"); + /// + /// // imgui will understand this and row spacing will be adjusted automatically. + /// } + /// ``` + /// + /// This functions returns true if the given column is **visible.** It is not + /// marked as must use, as you can still render commands into the not-visible column, + /// though you can choose to not as an optimization. + /// + /// # Panics + /// If `column_index >= ui.table_columm_count`, this function will panic. In `debug` releases, + /// we will panic on the Rust side, for a nicer error message, though in release, we will + /// panic in C++, which will result in an ugly stack overflow. pub fn table_set_column_index(&self, column_index: usize) -> bool { + #[cfg(debug_assertions)] + { + let size = self.table_column_count() as usize; + if column_index >= size { + panic!( + "column_index >= self.table_get_column_count().\ + Requested {}, but only have {} columns.", + column_index, size + ); + } + } + unsafe { sys::igTableSetColumnIndex(column_index as i32) } } + /// Specify label per column, with no flags and default sizing. You can avoid calling + /// this method entirely by using [begin_table_header]. + /// + /// # Example + /// ```rs + /// let ui: Ui<'static> = unimplemented!(); + /// + /// if let Some(_t) = ui.begin_table(im_str!("My Table"), 2) { + /// ui.table_setup_column(im_str!("One")); + /// ui.table_setup_column(im_str!("Two")); + /// ui.table_setup_column(im_str!("Three")); + /// ui.table_headers_row(); + /// + /// // call next_column/set_column_index and proceed like normal. + /// // the above code is the equivalent of just using `begin_table_header` + /// // but does allow for some columns to have headers and others to not + /// } + /// ``` + /// + /// Along with [table_headers_row](Self::table_headers_row), this method is used to create a header + /// row and automatically submit a table header for each column. + /// Headers are required to perform: reordering, sorting, and opening the context menu (though, + /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. pub fn table_setup_column(&self, str_id: &ImStr) { - self.table_setup_column_with_flags(str_id, TableColumnFlags::NONE) + self.table_setup_column_with(TableColumnSetup::new(str_id)) } - pub fn table_setup_column_with_flags(&self, str_id: &ImStr, flags: TableColumnFlags) { - self.table_setup_column_with_id(str_id, flags, 0.0, Id::Int(0)) - } - pub fn table_setup_column_with_id( - &self, - str_id: &ImStr, - flags: TableColumnFlags, - init_width_or_weight: f32, - user_id: Id, - ) { + /// Specify label per column, with data given in [TableColumnSetup]. You can avoid calling + /// this method entirely by using [begin_table_header]. + /// + /// See [table_setup_column](Self::table_setup_column) for an example of how to setup columns + /// yourself. + /// + /// Along with [table_headers_row](Self::table_headers_row), this method is used to create a header + /// row and automatically submit a table header for each column. + /// Headers are required to perform: reordering, sorting, and opening the context menu (though, + /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. + pub fn table_setup_column_with(&self, data: TableColumnSetup<'_>) { unsafe { sys::igTableSetupColumn( - str_id.as_ptr(), - flags.bits() as i32, - init_width_or_weight, - user_id.as_imgui_id(), + data.name.as_ptr(), + data.flags.bits() as i32, + data.init_width_or_weight, + data.user_id.as_imgui_id(), ) } } + /// Locks columns/rows so they stay visible when scrolled. Generally, you will be calling this + /// so that the header column is always visible (though go wild if you want). You can avoid + /// calling this entirely by passing `true` to [begin_table_header]. + /// + /// # Example + /// ```rs + /// let ui: Ui<'static> = unimplemented!(); + /// + /// const COLUMN_COUNT: usize = 3; + /// if let Some(_t) = ui.begin_table(im_str!("scroll-freeze-example"), COLUMN_COUNT) { + /// // locks the header row. Notice how we need to call it BEFORE `table_headers_row`. + /// ui.table_setup_scroll_freeze(1, COLUMN_COUNT); + /// ui.table_setup_column(im_str!("One")); + /// ui.table_setup_column(im_str!("Two")); + /// ui.table_setup_column(im_str!("Three")); + /// ui.table_headers_row(); + /// } + /// ``` pub fn table_setup_scroll_freeze(&self, locked_columns: i32, locked_rows: i32) { unsafe { sys::igTableSetupScrollFreeze(locked_columns, locked_rows); } } + + /// Along with [table_setup_column](Self::table_setup_column), this method is used + /// to create a header row and automatically submit a table header for each column. + /// + /// For an example of using this method, see [table_setup_column](Self::table_setup_column). + /// + /// Headers are required to perform: reordering, sorting, and opening the context menu (though, + /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. + /// + /// You may manually submit headers using [table_next_column] + [table_header] calls, but this is + /// only useful in some advanced use cases (e.g. adding custom widgets in header row). + /// See [table_header](Self::table_header) for more information. + /// + /// [table_next_column]: Self::table_next_column + /// [table_header]: Self::table_header pub fn table_headers_row(&self) { unsafe { sys::igTableHeadersRow(); } } + + /// Use this function to manually declare a column cell to be a header. You generally should + /// avoid using this outside of specific cases, such as custom widgets. Instead, + /// use [table_headers_row](Self::table_headers_row) and [table_setup_column](Self::table_setup_column). pub fn table_header(&self, label: &ImStr) { unsafe { sys::igTableHeader(label.as_ptr()); } } - //pub fn table_get_sort_specs(&self) -> &TableSortSpecs { + // pub fn table_get_sort_specs(&self) -> &TableSortSpecs { // unsafe { sys::igTableGetSortSpecs() } - //} + // } - pub fn table_get_column_count(&self) -> i32 { - unsafe { sys::igTableGetColumnCount() } - } - pub fn table_get_column_index(&self) -> i32 { - unsafe { sys::igTableGetColumnIndex() } - } - pub fn table_get_row_index(&self) -> i32 { - unsafe { sys::igTableGetRowIndex() } + /// Gets the numbers of columns in the current table. + pub fn table_column_count(&self) -> usize { + unsafe { sys::igTableGetColumnCount() as usize } } - pub fn table_get_column_flags(&self) -> TableColumnFlags { - self.table_get_column_flags_with_column(-1) + /// Gets the current column index in the current table. + pub fn table_column_index(&self) -> usize { + unsafe { sys::igTableGetColumnIndex() as usize } } - pub fn table_get_column_flags_with_column(&self, column_n: i32) -> TableColumnFlags { + + /// Gets the current row index in the current table. + pub fn table_row_index(&self) -> usize { + unsafe { sys::igTableGetRowIndex() as usize } + } + + /// Gets the flags on the current column in the current table. + pub fn table_column_flags(&self) -> TableColumnFlags { unsafe { - TableColumnFlags::from_bits_unchecked(sys::igTableGetColumnFlags(column_n) as u32) + TableColumnFlags::from_bits(sys::igTableGetColumnFlags(-1) as u32) + .expect("bad column flags") } } - pub fn table_set_bg_color(&self, target: TableBgTarget, color: [f32; 4]) { - self.table_set_bg_color_with_column(target, color, -1); + /// Gets the flags on the given column in the current table. To get the current column's + /// flags without having to call [table_column_index](Self::table_column_index), use + /// [table_column_flags](Self::table_column_flags). + pub fn table_column_flags_with_column(&self, column_n: usize) -> TableColumnFlags { + unsafe { + TableColumnFlags::from_bits(sys::igTableGetColumnFlags(column_n as i32) as u32) + .expect("bad column flags") + } } + + /// Gets the name of the current column. If there is no currently bound name + /// for this column, we will return an empty string. + /// + /// Use [table_column_name_with_column](Self::table_column_name_with_column) + /// for arbitrary indices. + pub fn table_column_name(&mut self) -> &ImStr { + unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(-1)) } + } + + /// Gets the name of a given column. If there is no currently bound name + /// for this column, we will return an empty string. + /// + /// Use [table_column_name](Self::table_column_name) for the current column. + pub fn table_column_name_with_column(&mut self, column: usize) -> &ImStr { + unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(column as i32)) } + } + + /// Sets the given background color for this column. See [TableBgTarget] + /// for more information on how colors work for tables. + /// + /// Use [table_set_bg_color_with_column](Self::table_set_bg_color_with_column) to set + /// for arbitrary indices. + pub fn table_set_bg_color(&self, target: TableBgTarget, color: impl Into) { + unsafe { + sys::igTableSetBgColor(target.bits() as i32, color.into().into(), -1); + } + } + + /// Sets the given background color for any column. See [TableBgTarget] + /// for more information on how colors work for tables. + /// + /// Use [table_set_bg_color](Self::table_set_bg_color) for the current column. pub fn table_set_bg_color_with_column( &self, target: TableBgTarget, - color: [f32; 4], - column_n: i32, + color: impl Into, + column_index: usize, ) { - let color = ImColor32::from(color); unsafe { - sys::igTableSetBgColor(target.bits() as i32, color.to_bits(), column_n); + sys::igTableSetBgColor( + target.bits() as i32, + color.into().into(), + column_index as i32, + ); } } } -pub struct TableHeader<'a> { +/// A struct containing all the data needed to setup a table column header +/// via [begin_table_header](Ui::begin_table_header) or [table_setup_column](Ui::table_setup_column). +pub struct TableColumnSetup<'a> { + /// The name of column to be displayed to users. pub name: &'a ImStr, + /// The flags this column will have. pub flags: TableColumnFlags, + /// The width or weight of the given column. pub init_width_or_weight: f32, + /// A user_id, primarily used in sorting operations. pub user_id: Id<'a>, } -impl<'a> TableHeader<'a> { +impl<'a> TableColumnSetup<'a> { pub fn new(name: &'a ImStr) -> Self { Self { name, @@ -292,12 +678,12 @@ impl<'a> TableHeader<'a> { } } -impl<'a> Default for TableHeader<'a> { +impl<'a> Default for TableColumnSetup<'a> { fn default() -> Self { Self { name: Default::default(), - flags: Default::default(), - init_width_or_weight: Default::default(), + flags: TableColumnFlags::empty(), + init_width_or_weight: 0.0, user_id: Id::Int(0), } } @@ -308,6 +694,6 @@ create_token!( /// or by dropping. pub struct TableToken<'ui>; - /// Pops a change from the font stack. + /// Ends the table. drop { sys::igEndTable() } ); From eb455f032ceaf14c3f7174eb3a01110811dd4e06 Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Mon, 13 Sep 2021 13:18:06 -0400 Subject: [PATCH 4/9] fmt --- imgui/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 07cccec..04d8a76 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -303,8 +303,7 @@ impl<'a> Id<'a> { Id::Int(i) => { let p = *i as *const std::os::raw::c_void; sys::igGetIDPtr(p) - } - // Id::ImGuiID(n) => *n, + } // Id::ImGuiID(n) => *n, } } } From 9d4320663372ea6860021f86ee406ca44edf923a Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Mon, 13 Sep 2021 16:49:00 -0400 Subject: [PATCH 5/9] added controls for sorting...which should finish this guy off! --- imgui-examples/examples/tables_api.rs | 82 ++++++++++++ imgui/src/tables.rs | 172 ++++++++++++++++++++++---- 2 files changed, 229 insertions(+), 25 deletions(-) diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs index 36865eb..d21e144 100644 --- a/imgui-examples/examples/tables_api.rs +++ b/imgui-examples/examples/tables_api.rs @@ -5,6 +5,24 @@ mod support; fn main() { let system = support::init(file!()); + let mut humans = vec![ + HumanData { + name: "Joonas", + favorite_number: 102, + favorite_fruit_maybe: "Dutch", + }, + HumanData { + name: "Thom", + favorite_number: 314, + favorite_fruit_maybe: "Rice", + }, + HumanData { + name: "Jack", + favorite_number: 611, + favorite_fruit_maybe: "Mangoes", + }, + ]; + let mut t2_flags = TableFlags::REORDERABLE | TableFlags::HIDEABLE | TableFlags::RESIZABLE @@ -129,6 +147,70 @@ fn main() { ui.text(fruit[i]); } } + + ui.separator(); + + ui.text("Here's a table you can sort!"); + ui.text("Check the code to see the two methods of doing it."); + + if let Some(_t) = ui.begin_table_header_with_flags( + im_str!("table-headers3"), + [ + TableColumnSetup::new(im_str!("Name")), + TableColumnSetup::new(im_str!("Favorite Number")), + TableColumnSetup::new(im_str!("Favorite fruit")), + ], + TableFlags::SORTABLE, + ) { + if let Some(mut sort_data) = ui.table_sort_specs_mut() { + sort_data + .conditional_sort(|specs| HumanData::sort_humans(&mut humans, specs)); + + // Can also sort this other way... + // if sort_data.should_sort() { + // HumanData::sort_humans(&mut humans, sort_data.specs()); + // sort_data.set_sorted(); + // } + } + + for i in 0..3 { + ui.table_next_column(); + ui.text(humans[i].name); + + ui.table_next_column(); + ui.text(humans[i].favorite_number.to_string()); + + ui.table_next_column(); + ui.text(humans[i].favorite_fruit_maybe); + } + } }); }); } +struct HumanData { + name: &'static str, + favorite_number: usize, + favorite_fruit_maybe: &'static str, +} + +impl HumanData { + pub fn sort_humans(humans: &mut Vec, specs: Specs<'_>) { + let spec = specs.iter().next().unwrap(); + if let Some(kind) = spec.sort_direction() { + match kind { + TableSortDirection::Ascending => match spec.column_idx() { + 0 => humans.sort_by_key(|h| h.name), + 1 => humans.sort_by_key(|h| h.favorite_number), + 2 => humans.sort_by_key(|h| h.favorite_fruit_maybe), + _ => unimplemented!(), + }, + TableSortDirection::Descending => match spec.column_idx() { + 0 => humans.sort_by_key(|h| std::cmp::Reverse(h.name)), + 1 => humans.sort_by_key(|h| std::cmp::Reverse(h.favorite_number)), + 2 => humans.sort_by_key(|h| std::cmp::Reverse(h.favorite_fruit_maybe)), + _ => unimplemented!(), + }, + } + } + } +} diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 3eb235b..418d849 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use bitflags::bitflags; use crate::sys; @@ -240,6 +242,12 @@ bitflags! { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum TableSortDirection { + Ascending, + Descending, +} + impl<'ui> Ui<'ui> { /// Begins a table with no flags and with standard sizing contraints. /// @@ -294,13 +302,19 @@ impl<'ui> Ui<'ui> { ) }; - should_render.then(|| TableToken::new(self)) + // todo: once msrv is 1.54, convert this to .then(||) + if should_render { + Some(TableToken::new(self)) + } else { + None + } } /// Begins a table with no flags and with standard sizing contraints. /// /// Takes an array of table header information, the length of which determines /// how many columns will be created. + #[cfg(feature = "min-const-generics")] pub fn begin_table_header<'a, const N: usize>( &self, str_id: &ImStr, @@ -313,6 +327,7 @@ impl<'ui> Ui<'ui> { /// /// Takes an array of table header information, the length of which determines /// how many columns will be created. + #[cfg(feature = "min-const-generics")] pub fn begin_table_header_with_flags<'a, const N: usize>( &self, str_id: &ImStr, @@ -326,6 +341,7 @@ impl<'ui> Ui<'ui> { /// and gives users the most flexibility. /// Takes an array of table header information, the length of which determines /// how many columns will be created. + #[cfg(feature = "min-const-generics")] pub fn begin_table_header_with_sizing<'a, const N: usize>( &self, str_id: &ImStr, @@ -562,19 +578,17 @@ impl<'ui> Ui<'ui> { } } - /// Use this function to manually declare a column cell to be a header. You generally should - /// avoid using this outside of specific cases, such as custom widgets. Instead, - /// use [table_headers_row](Self::table_headers_row) and [table_setup_column](Self::table_setup_column). + /// Use this function to manually declare a column cell to be a header. + /// + /// You generally should avoid using this outside of specific cases, + /// such as custom widgets. Instead, use [table_headers_row](Self::table_headers_row) + /// and [table_setup_column](Self::table_setup_column). pub fn table_header(&self, label: &ImStr) { unsafe { sys::igTableHeader(label.as_ptr()); } } - // pub fn table_get_sort_specs(&self) -> &TableSortSpecs { - // unsafe { sys::igTableGetSortSpecs() } - // } - /// Gets the numbers of columns in the current table. pub fn table_column_count(&self) -> usize { unsafe { sys::igTableGetColumnCount() as usize } @@ -590,6 +604,23 @@ impl<'ui> Ui<'ui> { unsafe { sys::igTableGetRowIndex() as usize } } + /// Gets the name of the current column. If there is no currently bound name + /// for this column, we will return an empty string. + /// + /// Use [table_column_name_with_column](Self::table_column_name_with_column) + /// for arbitrary indices. + pub fn table_column_name(&mut self) -> &ImStr { + unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(-1)) } + } + + /// Gets the name of a given column. If there is no currently bound name + /// for this column, we will return an empty string. + /// + /// Use [table_column_name](Self::table_column_name) for the current column. + pub fn table_column_name_with_column(&mut self, column: usize) -> &ImStr { + unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(column as i32)) } + } + /// Gets the flags on the current column in the current table. pub fn table_column_flags(&self) -> TableColumnFlags { unsafe { @@ -608,23 +639,6 @@ impl<'ui> Ui<'ui> { } } - /// Gets the name of the current column. If there is no currently bound name - /// for this column, we will return an empty string. - /// - /// Use [table_column_name_with_column](Self::table_column_name_with_column) - /// for arbitrary indices. - pub fn table_column_name(&mut self) -> &ImStr { - unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(-1)) } - } - - /// Gets the name of a given column. If there is no currently bound name - /// for this column, we will return an empty string. - /// - /// Use [table_column_name](Self::table_column_name) for the current column. - pub fn table_column_name_with_column(&mut self, column: usize) -> &ImStr { - unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(column as i32)) } - } - /// Sets the given background color for this column. See [TableBgTarget] /// for more information on how colors work for tables. /// @@ -654,6 +668,18 @@ impl<'ui> Ui<'ui> { ); } } + + /// Gets the sorting data for a table. This will be `None` when not sorting. + pub fn table_sort_specs_mut(&self) -> Option> { + unsafe { + let value = sys::igTableGetSortSpecs(); + if value.is_null() { + None + } else { + Some(TableSortSpecsMut(value, PhantomData)) + } + } + } } /// A struct containing all the data needed to setup a table column header @@ -689,6 +715,102 @@ impl<'a> Default for TableColumnSetup<'a> { } } +/// A wrapper around table sort specs. +/// +/// To use this simply, use `conditional_sort` and provide a closure -- +/// if you should sort your data, then the closure will be ran and imgui +/// will be informed that your data is sorted. +/// +/// For manual control (such as if sorting can fail), use [should_sort] to +/// check if you should sort your data, sort your data using [specs] for information +/// on how to sort it, and then [set_sorted] to indicate that the data is sorted. +pub struct TableSortSpecsMut<'ui>(*mut sys::ImGuiTableSortSpecs, PhantomData>); + +impl TableSortSpecsMut<'_> { + /// Gets the specs for a given sort. In most scenarios, this will be a slice of 1 entry. + pub fn specs(&self) -> Specs<'_> { + let value = + unsafe { std::slice::from_raw_parts((*self.0).Specs, (*self.0).SpecsCount as usize) }; + + Specs(value) + } + + /// Returns true if the data should be sorted. + pub fn should_sort(&self) -> bool { + unsafe { (*self.0).SpecsDirty } + } + + /// Sets the internal flag that the data has been sorted. + pub fn set_sorted(&mut self) { + unsafe { + (*self.0).SpecsDirty = false; + } + } + + /// Provide a closure, which will receive the Specs for a sort. + /// + /// If you should sort the data, the closure will run, and ImGui will be + /// told that the data has been sorted. + /// + /// If you need manual control over sorting, consider using [should_sort], [specs], + /// and [set_sorted] youself. + pub fn conditional_sort(mut self, mut f: impl FnMut(Specs<'_>)) { + let is_dirty = self.should_sort(); + + if is_dirty { + f(self.specs()); + } + + self.set_sorted(); + } +} + +/// A wrapper around a slice of [TableColumnSortSpecs]. +/// +/// This slice may be 0 if [SORT_TRISTATE] is true, may be > 1 is [SORT_MULTI] is true, +/// but is generally == 1. +/// +/// Consume this struct as an iterator. +pub struct Specs<'a>(&'a [sys::ImGuiTableColumnSortSpecs]); + +impl<'a> Specs<'a> { + pub fn iter(self) -> impl Iterator> { + self.0.iter().map(|v| TableColumnSortSpecs(v)) + } +} + +pub struct TableColumnSortSpecs<'a>(&'a sys::ImGuiTableColumnSortSpecs); +impl<'a> TableColumnSortSpecs<'a> { + /// User id of the column (if specified by a TableSetupColumn() call) + pub fn column_user_id(&self) -> sys::ImGuiID { + self.0.ColumnUserID + } + + /// Index of the column + pub fn column_idx(&self) -> usize { + self.0.ColumnIndex as usize + } + + /// Index within parent [Specs] slice where this was found -- always stored in order starting + /// from 0, tables sorted on a single criteria will always have a 0 here. + /// + /// Generally, you don't need to access this, as it's the same as calling `specs.iter().enumerate()`. + pub fn sort_order(&self) -> usize { + self.0.SortOrder as usize + } + + /// Gets the sort direction for the given column. This will nearly always be `Some` if you + /// can access it. + pub fn sort_direction(&self) -> Option { + match self.0.SortDirection() { + 0 => None, + 1 => Some(TableSortDirection::Ascending), + 2 => Some(TableSortDirection::Descending), + _ => unimplemented!(), + } + } +} + create_token!( /// Tracks a table which can be rendered onto, ending with `.end()` /// or by dropping. From 3ffe82e4884acaa9ed859db88b18c989ef0fc938 Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Mon, 13 Sep 2021 16:54:02 -0400 Subject: [PATCH 6/9] clipppy --- imgui-examples/examples/tables_api.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs index d21e144..fc4ee30 100644 --- a/imgui-examples/examples/tables_api.rs +++ b/imgui-examples/examples/tables_api.rs @@ -162,7 +162,7 @@ fn main() { ], TableFlags::SORTABLE, ) { - if let Some(mut sort_data) = ui.table_sort_specs_mut() { + if let Some(sort_data) = ui.table_sort_specs_mut() { sort_data .conditional_sort(|specs| HumanData::sort_humans(&mut humans, specs)); @@ -173,15 +173,15 @@ fn main() { // } } - for i in 0..3 { + for human in humans.iter() { ui.table_next_column(); - ui.text(humans[i].name); + ui.text(human.name); ui.table_next_column(); - ui.text(humans[i].favorite_number.to_string()); + ui.text(human.favorite_number.to_string()); ui.table_next_column(); - ui.text(humans[i].favorite_fruit_maybe); + ui.text(human.favorite_fruit_maybe); } } }); From b57cb04076b5aaa90d0cd8efb0681c253b7b3c23 Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Mon, 13 Sep 2021 17:01:07 -0400 Subject: [PATCH 7/9] fixes cargo deadlinks --- imgui/src/tables.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 418d849..082134d 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -252,7 +252,7 @@ impl<'ui> Ui<'ui> { /// Begins a table with no flags and with standard sizing contraints. /// /// This does no work on styling the headers (the top row) -- see either - /// [begin_table_with_headers](Self::begin_table_with_headers) or the more complex + /// [begin_table_header](Self::begin_table_header) or the more complex /// [table_setup_column](Self::table_setup_column). /// /// **NB:** after you begin a table (and after setting up ) @@ -265,7 +265,7 @@ impl<'ui> Ui<'ui> { /// Begins a table with flags and standard sizing contraints. /// /// This does no work on styling the headers (the top row) -- see either - /// [begin_table_with_headers](Self::begin_table_with_headers) or the more complex + /// [begin_table_header](Self::begin_table_header) or the more complex /// [table_setup_column](Self::table_setup_column). #[inline] pub fn begin_table_with_flags( @@ -383,8 +383,8 @@ impl<'ui> Ui<'ui> { /// Setting a flag here will make the next row a "header" now, which may /// require setup of column data. /// - /// See [table_next_row] for information on how moving rows work. To set the row - /// with a given height, see [table_next_row_with_height]. + /// See [table_next_row](Self::table_next_row) for information on how moving rows work. To set the row + /// with a given height, see [table_next_row_with_height](Self::table_next_row_with_height). #[inline] pub fn table_next_row_with_flags(&self, flags: TableRowFlags) { self.table_next_row_with_height(flags, 0.0); @@ -393,7 +393,7 @@ impl<'ui> Ui<'ui> { /// Moves a table to the next row (ie, down), with the given flags, /// and with the given minimum height. /// - /// See [table_next_row] for information on how moving rows work. + /// See [table_next_row](Self::table_next_row) for information on how moving rows work. #[inline] pub fn table_next_row_with_height(&self, flags: TableRowFlags, min_row_height: f32) { unsafe { @@ -487,7 +487,7 @@ impl<'ui> Ui<'ui> { } /// Specify label per column, with no flags and default sizing. You can avoid calling - /// this method entirely by using [begin_table_header]. + /// this method entirely by using [begin_table_header](Self::begin_table_header). /// /// # Example /// ```rs @@ -508,13 +508,13 @@ impl<'ui> Ui<'ui> { /// Along with [table_headers_row](Self::table_headers_row), this method is used to create a header /// row and automatically submit a table header for each column. /// Headers are required to perform: reordering, sorting, and opening the context menu (though, - /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. + /// the context menu can also be made available in columns body using [TableFlags::CONTEXT_MENU_IN_BODY]. pub fn table_setup_column(&self, str_id: &ImStr) { self.table_setup_column_with(TableColumnSetup::new(str_id)) } /// Specify label per column, with data given in [TableColumnSetup]. You can avoid calling - /// this method entirely by using [begin_table_header]. + /// this method entirely by using [begin_table_header](Self::begin_table_header). /// /// See [table_setup_column](Self::table_setup_column) for an example of how to setup columns /// yourself. @@ -522,7 +522,7 @@ impl<'ui> Ui<'ui> { /// Along with [table_headers_row](Self::table_headers_row), this method is used to create a header /// row and automatically submit a table header for each column. /// Headers are required to perform: reordering, sorting, and opening the context menu (though, - /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. + /// the context menu can also be made available in columns body using [TableFlags::CONTEXT_MENU_IN_BODY]. pub fn table_setup_column_with(&self, data: TableColumnSetup<'_>) { unsafe { sys::igTableSetupColumn( @@ -536,7 +536,7 @@ impl<'ui> Ui<'ui> { /// Locks columns/rows so they stay visible when scrolled. Generally, you will be calling this /// so that the header column is always visible (though go wild if you want). You can avoid - /// calling this entirely by passing `true` to [begin_table_header]. + /// calling this entirely by passing `true` to [begin_table_header](Self::begin_table_header). /// /// # Example /// ```rs @@ -564,7 +564,7 @@ impl<'ui> Ui<'ui> { /// For an example of using this method, see [table_setup_column](Self::table_setup_column). /// /// Headers are required to perform: reordering, sorting, and opening the context menu (though, - /// the context menu can also be made available in columns body using [TableFlags::ContextMenuInBody]. + /// the context menu can also be made available in columns body using [TableFlags::CONTEXT_MENU_IN_BODY]. /// /// You may manually submit headers using [table_next_column] + [table_header] calls, but this is /// only useful in some advanced use cases (e.g. adding custom widgets in header row). @@ -717,13 +717,18 @@ impl<'a> Default for TableColumnSetup<'a> { /// A wrapper around table sort specs. /// -/// To use this simply, use `conditional_sort` and provide a closure -- +/// To use this simply, use [conditional_sort] and provide a closure -- /// if you should sort your data, then the closure will be ran and imgui /// will be informed that your data is sorted. /// /// For manual control (such as if sorting can fail), use [should_sort] to /// check if you should sort your data, sort your data using [specs] for information /// on how to sort it, and then [set_sorted] to indicate that the data is sorted. +/// +/// [conditional_sort]: Self::conditional_sort +/// [should_sort]: Self::should_sort +/// [specs]: Self::specs +/// [set_sorted]: Self::set_sorted pub struct TableSortSpecsMut<'ui>(*mut sys::ImGuiTableSortSpecs, PhantomData>); impl TableSortSpecsMut<'_> { @@ -754,6 +759,10 @@ impl TableSortSpecsMut<'_> { /// /// If you need manual control over sorting, consider using [should_sort], [specs], /// and [set_sorted] youself. + /// + /// [should_sort]: Self::should_sort + /// [specs]: Self::specs + /// [set_sorted]: Self::set_sorted pub fn conditional_sort(mut self, mut f: impl FnMut(Specs<'_>)) { let is_dirty = self.should_sort(); @@ -767,7 +776,7 @@ impl TableSortSpecsMut<'_> { /// A wrapper around a slice of [TableColumnSortSpecs]. /// -/// This slice may be 0 if [SORT_TRISTATE] is true, may be > 1 is [SORT_MULTI] is true, +/// This slice may be 0 if [TableFlags::SORT_TRISTATE] is true, may be > 1 is [TableFlags::SORT_MULTI] is true, /// but is generally == 1. /// /// Consume this struct as an iterator. From be9fd26595fdb77f7f3841bd6c0803cde13417f3 Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Mon, 13 Sep 2021 17:32:50 -0400 Subject: [PATCH 8/9] rebased --- imgui-examples/examples/tables_api.rs | 38 ++++++------- imgui/src/lib.rs | 6 ++ imgui/src/tables.rs | 79 +++++++++++++++------------ 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs index fc4ee30..ae5c63e 100644 --- a/imgui-examples/examples/tables_api.rs +++ b/imgui-examples/examples/tables_api.rs @@ -29,10 +29,10 @@ fn main() { | TableFlags::NO_BORDERS_IN_BODY; system.main_loop(move |_, ui| { - Window::new(im_str!("Input text callbacks")) + Window::new("Input text callbacks") .size([800.0, 400.0], Condition::FirstUseEver) .build(ui, || { - if let Some(_t) = ui.begin_table(im_str!("Basic-Table"), 3) { + if let Some(_t) = ui.begin_table("Basic-Table", 3) { // we must also call `next_row` here, because we declined // to set up header rows. If we set up header rows ourselves, // we will call `table_header_rows` instead, and if we use @@ -74,11 +74,11 @@ fn main() { ui.separator(); ui.text("Let's add some headers"); if let Some(_t) = ui.begin_table_header( - im_str!("table-headers"), + "table-headers", [ - TableColumnSetup::new(im_str!("Name")), - TableColumnSetup::new(im_str!("Age")), - TableColumnSetup::new(im_str!("Favorite fruit")), + TableColumnSetup::new("Name"), + TableColumnSetup::new("Age"), + TableColumnSetup::new("Favorite fruit"), ], ) { // note that we DON'T have to call "table_next_row" here -- that's taken care @@ -109,22 +109,18 @@ fn main() { Notice how toggling these checkboxes changes the context menu.", ); - ui.checkbox_flags( - im_str!("Reorderable"), - &mut t2_flags, - TableFlags::REORDERABLE, - ); + ui.checkbox_flags("Reorderable", &mut t2_flags, TableFlags::REORDERABLE); ui.same_line(); - ui.checkbox_flags(im_str!("Hideable"), &mut t2_flags, TableFlags::HIDEABLE); + ui.checkbox_flags("Hideable", &mut t2_flags, TableFlags::HIDEABLE); ui.same_line(); - ui.checkbox_flags(im_str!("Resizable"), &mut t2_flags, TableFlags::RESIZABLE); + ui.checkbox_flags("Resizable", &mut t2_flags, TableFlags::RESIZABLE); if let Some(_t) = ui.begin_table_header_with_flags( - im_str!("table-headers2"), + "table-headers2", [ - TableColumnSetup::new(im_str!("Name")), - TableColumnSetup::new(im_str!("Age")), - TableColumnSetup::new(im_str!("Favorite fruit")), + TableColumnSetup::new("Name"), + TableColumnSetup::new("Age"), + TableColumnSetup::new("Favorite fruit"), ], t2_flags, ) { @@ -154,11 +150,11 @@ fn main() { ui.text("Check the code to see the two methods of doing it."); if let Some(_t) = ui.begin_table_header_with_flags( - im_str!("table-headers3"), + "table-headers3", [ - TableColumnSetup::new(im_str!("Name")), - TableColumnSetup::new(im_str!("Favorite Number")), - TableColumnSetup::new(im_str!("Favorite fruit")), + TableColumnSetup::new("Name"), + TableColumnSetup::new("Favorite Number"), + TableColumnSetup::new("Favorite fruit"), ], TableFlags::SORTABLE, ) { diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 04d8a76..55164ab 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -309,6 +309,12 @@ impl<'a> Id<'a> { } } +impl<'a> Default for Id<'a> { + fn default() -> Self { + Self::Int(0) + } +} + // Widgets: Input impl<'ui> Ui<'ui> { #[doc(alias = "InputText", alias = "InputTextWithHint")] diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 082134d..726450b 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -1,9 +1,10 @@ +use std::ffi::CStr; use std::marker::PhantomData; use bitflags::bitflags; use crate::sys; -use crate::{Id, ImColor32, ImStr, Ui}; +use crate::{Id, ImColor32, Ui}; bitflags! { /// Flags passed to `begin_table` methods. @@ -258,7 +259,11 @@ impl<'ui> Ui<'ui> { /// **NB:** after you begin a table (and after setting up ) #[inline] #[must_use = "if return is dropped immediately, table is ended immediately."] - pub fn begin_table(&self, str_id: &ImStr, column_count: i32) -> Option> { + pub fn begin_table( + &self, + str_id: impl AsRef, + column_count: i32, + ) -> Option> { self.begin_table_with_flags(str_id, column_count, TableFlags::empty()) } @@ -270,7 +275,7 @@ impl<'ui> Ui<'ui> { #[inline] pub fn begin_table_with_flags( &self, - str_id: &ImStr, + str_id: impl AsRef, column_count: i32, flags: TableFlags, ) -> Option> { @@ -286,7 +291,7 @@ impl<'ui> Ui<'ui> { #[inline] pub fn begin_table_with_sizing( &self, - str_id: &ImStr, + str_id: impl AsRef, column: i32, flags: TableFlags, outer_size: [f32; 2], @@ -294,7 +299,7 @@ impl<'ui> Ui<'ui> { ) -> Option> { let should_render = unsafe { sys::igBeginTable( - str_id.as_ptr(), + self.scratch_txt(str_id), column, flags.bits() as i32, outer_size.into(), @@ -315,10 +320,10 @@ impl<'ui> Ui<'ui> { /// Takes an array of table header information, the length of which determines /// how many columns will be created. #[cfg(feature = "min-const-generics")] - pub fn begin_table_header<'a, const N: usize>( + pub fn begin_table_header<'a, Name: AsRef, const N: usize>( &self, - str_id: &ImStr, - column_data: [TableColumnSetup<'a>; N], + str_id: impl AsRef, + column_data: [TableColumnSetup<'a, Name>; N], ) -> Option> { self.begin_table_header_with_flags(str_id, column_data, TableFlags::empty()) } @@ -328,10 +333,10 @@ impl<'ui> Ui<'ui> { /// Takes an array of table header information, the length of which determines /// how many columns will be created. #[cfg(feature = "min-const-generics")] - pub fn begin_table_header_with_flags<'a, const N: usize>( + pub fn begin_table_header_with_flags<'a, Name: AsRef, const N: usize>( &self, - str_id: &ImStr, - column_data: [TableColumnSetup<'a>; N], + str_id: impl AsRef, + column_data: [TableColumnSetup<'a, Name>; N], flags: TableFlags, ) -> Option> { self.begin_table_header_with_sizing(str_id, column_data, flags, [0.0, 0.0], 0.0) @@ -342,10 +347,10 @@ impl<'ui> Ui<'ui> { /// Takes an array of table header information, the length of which determines /// how many columns will be created. #[cfg(feature = "min-const-generics")] - pub fn begin_table_header_with_sizing<'a, const N: usize>( + pub fn begin_table_header_with_sizing<'a, Name: AsRef, const N: usize>( &self, - str_id: &ImStr, - column_data: [TableColumnSetup<'a>; N], + str_id: impl AsRef, + column_data: [TableColumnSetup<'a, Name>; N], flags: TableFlags, outer_size: [f32; 2], inner_width: f32, @@ -509,7 +514,7 @@ impl<'ui> Ui<'ui> { /// row and automatically submit a table header for each column. /// Headers are required to perform: reordering, sorting, and opening the context menu (though, /// the context menu can also be made available in columns body using [TableFlags::CONTEXT_MENU_IN_BODY]. - pub fn table_setup_column(&self, str_id: &ImStr) { + pub fn table_setup_column(&self, str_id: impl AsRef) { self.table_setup_column_with(TableColumnSetup::new(str_id)) } @@ -523,10 +528,10 @@ impl<'ui> Ui<'ui> { /// row and automatically submit a table header for each column. /// Headers are required to perform: reordering, sorting, and opening the context menu (though, /// the context menu can also be made available in columns body using [TableFlags::CONTEXT_MENU_IN_BODY]. - pub fn table_setup_column_with(&self, data: TableColumnSetup<'_>) { + pub fn table_setup_column_with>(&self, data: TableColumnSetup<'_, N>) { unsafe { sys::igTableSetupColumn( - data.name.as_ptr(), + self.scratch_txt(data.name), data.flags.bits() as i32, data.init_width_or_weight, data.user_id.as_imgui_id(), @@ -583,9 +588,9 @@ impl<'ui> Ui<'ui> { /// You generally should avoid using this outside of specific cases, /// such as custom widgets. Instead, use [table_headers_row](Self::table_headers_row) /// and [table_setup_column](Self::table_setup_column). - pub fn table_header(&self, label: &ImStr) { + pub fn table_header(&self, label: impl AsRef) { unsafe { - sys::igTableHeader(label.as_ptr()); + sys::igTableHeader(self.scratch_txt(label)); } } @@ -609,16 +614,26 @@ impl<'ui> Ui<'ui> { /// /// Use [table_column_name_with_column](Self::table_column_name_with_column) /// for arbitrary indices. - pub fn table_column_name(&mut self) -> &ImStr { - unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(-1)) } + pub fn table_column_name(&mut self) -> &str { + unsafe { + // imgui uses utf8...though that is a continuous process there. + CStr::from_ptr(sys::igTableGetColumnName(-1)) + .to_str() + .unwrap() + } } /// Gets the name of a given column. If there is no currently bound name /// for this column, we will return an empty string. /// /// Use [table_column_name](Self::table_column_name) for the current column. - pub fn table_column_name_with_column(&mut self, column: usize) -> &ImStr { - unsafe { ImStr::from_ptr_unchecked(sys::igTableGetColumnName(column as i32)) } + pub fn table_column_name_with_column(&mut self, column: usize) -> &str { + unsafe { + // imgui uses utf8...though that is a continuous process there. + CStr::from_ptr(sys::igTableGetColumnName(column as i32)) + .to_str() + .unwrap() + } } /// Gets the flags on the current column in the current table. @@ -684,9 +699,10 @@ impl<'ui> Ui<'ui> { /// A struct containing all the data needed to setup a table column header /// via [begin_table_header](Ui::begin_table_header) or [table_setup_column](Ui::table_setup_column). -pub struct TableColumnSetup<'a> { +#[derive(Debug, Default)] +pub struct TableColumnSetup<'a, Name> { /// The name of column to be displayed to users. - pub name: &'a ImStr, + pub name: Name, /// The flags this column will have. pub flags: TableColumnFlags, /// The width or weight of the given column. @@ -695,19 +711,10 @@ pub struct TableColumnSetup<'a> { pub user_id: Id<'a>, } -impl<'a> TableColumnSetup<'a> { - pub fn new(name: &'a ImStr) -> Self { +impl<'a, Name: AsRef> TableColumnSetup<'a, Name> { + pub fn new(name: Name) -> Self { Self { name, - ..Default::default() - } - } -} - -impl<'a> Default for TableColumnSetup<'a> { - fn default() -> Self { - Self { - name: Default::default(), flags: TableColumnFlags::empty(), init_width_or_weight: 0.0, user_id: Id::Int(0), From 6e1f359d08f8c33d31664a80c3eec20427de52ea Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Tue, 14 Sep 2021 11:14:09 -0400 Subject: [PATCH 9/9] rebased and waiting for green --- imgui/src/lib.rs | 6 +++--- imgui/src/tables.rs | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 55164ab..91a09de 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -294,15 +294,15 @@ impl<'a> Id<'a> { fn as_imgui_id(&self) -> sys::ImGuiID { unsafe { match self { - Id::Ptr(p) => sys::igGetIDPtr(*p), + Id::Ptr(p) => sys::igGetID_Ptr(*p), Id::Str(s) => { let s1 = s.as_ptr() as *const std::os::raw::c_char; let s2 = s1.add(s.len()); - sys::igGetIDStrStr(s1, s2) + sys::igGetID_StrStr(s1, s2) } Id::Int(i) => { let p = *i as *const std::os::raw::c_void; - sys::igGetIDPtr(p) + sys::igGetID_Ptr(p) } // Id::ImGuiID(n) => *n, } } diff --git a/imgui/src/tables.rs b/imgui/src/tables.rs index 726450b..bfda31e 100644 --- a/imgui/src/tables.rs +++ b/imgui/src/tables.rs @@ -684,7 +684,30 @@ impl<'ui> Ui<'ui> { } } + /// Change user accessible enabled/disabled state of the current column. + /// + /// Set to false to hide the column. Users can use the context menu to change + /// this themselves by right-clicking in headers, or right-clicking in columns body + /// if [TableFlags::CONTEXT_MENU_IN_BODY]. + /// + /// Use [table_set_enabled_with_column](Self::table_set_enabled_with_column) to set + /// for arbitrary indices. + pub fn table_set_enabled(&self, enabled: bool) { + unsafe { sys::igTableSetColumnEnabled(-1, enabled) } + } + + /// Change user accessible enabled/disabled state of the current column. + /// + /// Set to false to hide the column. Users can use the context menu to change + /// this themselves by right-clicking in headers, or right-clicking in columns body + /// if [TableFlags::CONTEXT_MENU_IN_BODY]. + pub fn table_set_enabled_with_column(&self, enabled: bool, column_idx: usize) { + unsafe { sys::igTableSetColumnEnabled(column_idx as i32, enabled) } + } + /// Gets the sorting data for a table. This will be `None` when not sorting. + /// + /// See the examples folder for how to use the sorting API. pub fn table_sort_specs_mut(&self) -> Option> { unsafe { let value = sys::igTableGetSortSpecs();