added controls for sorting...which should finish this guy off!

This commit is contained in:
Jack Mac 2021-09-13 16:49:00 -04:00
parent eb455f032c
commit 9d43206633
2 changed files with 229 additions and 25 deletions

View File

@ -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<Self>, 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!(),
},
}
}
}
}

View File

@ -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<TableSortSpecsMut<'_>> {
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<Ui<'ui>>);
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<Item = TableColumnSortSpecs<'a>> {
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<TableSortDirection> {
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.