#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::as_conversions
)]
use crate::draw::ImColor32;
use crate::sys;
use crate::ui::Ui;
use crate::widget::{
TableColumnFlags, TableColumnIndent, TableColumnStateFlags, TableColumnWidth, TableFlags,
TableOptions, TableSizingPolicy,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::ffi::CStr;
#[derive(Clone, Debug)]
pub struct TableColumnSetup<Name> {
pub name: Name,
pub flags: TableColumnFlags,
pub width: Option<TableColumnWidth>,
pub indent: Option<TableColumnIndent>,
pub user_id: u32,
}
impl<Name> TableColumnSetup<Name> {
pub fn new(name: Name) -> Self {
Self {
name,
flags: TableColumnFlags::NONE,
width: None,
indent: None,
user_id: 0,
}
}
pub fn flags(mut self, flags: TableColumnFlags) -> Self {
self.flags = flags;
self
}
pub fn fixed_width(mut self, width: f32) -> Self {
self.width = Some(TableColumnWidth::Fixed(width));
self
}
pub fn stretch_weight(mut self, weight: f32) -> Self {
self.width = Some(TableColumnWidth::Stretch(weight));
self
}
pub fn indent(mut self, indent: TableColumnIndent) -> Self {
self.indent = Some(indent);
self
}
pub fn indent_enabled(mut self, enabled: bool) -> Self {
self.indent = Some(if enabled {
TableColumnIndent::Enable
} else {
TableColumnIndent::Disable
});
self
}
pub fn user_id(mut self, id: u32) -> Self {
self.user_id = id;
self
}
}
impl Ui {
pub fn table<'ui>(&'ui self, str_id: impl Into<Cow<'ui, str>>) -> TableBuilder<'ui> {
TableBuilder::new(self, str_id)
}
#[must_use = "if return is dropped immediately, table is ended immediately."]
pub fn begin_table(
&self,
str_id: impl AsRef<str>,
column_count: usize,
) -> Option<TableToken<'_>> {
self.begin_table_with_flags(str_id, column_count, TableFlags::NONE)
}
#[must_use = "if return is dropped immediately, table is ended immediately."]
pub fn begin_table_with_flags(
&self,
str_id: impl AsRef<str>,
column_count: usize,
flags: impl Into<TableOptions>,
) -> Option<TableToken<'_>> {
self.begin_table_with_sizing(str_id, column_count, flags, [0.0, 0.0], 0.0)
}
#[must_use = "if return is dropped immediately, table is ended immediately."]
pub fn begin_table_with_sizing(
&self,
str_id: impl AsRef<str>,
column_count: usize,
flags: impl Into<TableOptions>,
outer_size: impl Into<[f32; 2]>,
inner_width: f32,
) -> Option<TableToken<'_>> {
let options = flags.into();
let str_id_ptr = self.scratch_txt(str_id);
let outer_size_vec: sys::ImVec2 = outer_size.into().into();
let should_render = unsafe {
sys::igBeginTable(
str_id_ptr,
column_count as i32,
options.raw(),
outer_size_vec,
inner_width,
)
};
if should_render {
Some(TableToken::new(self))
} else {
None
}
}
#[must_use = "if return is dropped immediately, table is ended immediately."]
pub fn begin_table_header<Name: AsRef<str>, const N: usize>(
&self,
str_id: impl AsRef<str>,
column_data: [TableColumnSetup<Name>; N],
) -> Option<TableToken<'_>> {
self.begin_table_header_with_flags(str_id, column_data, TableFlags::NONE)
}
#[must_use = "if return is dropped immediately, table is ended immediately."]
pub fn begin_table_header_with_flags<Name: AsRef<str>, const N: usize>(
&self,
str_id: impl AsRef<str>,
column_data: [TableColumnSetup<Name>; N],
flags: impl Into<TableOptions>,
) -> Option<TableToken<'_>> {
if let Some(token) = self.begin_table_with_flags(str_id, N, flags) {
for column in &column_data {
self.table_setup_column_with_indent(
&column.name,
column.flags,
column.width,
column.indent,
column.user_id,
);
}
self.table_headers_row();
Some(token)
} else {
None
}
}
pub fn table_setup_column(
&self,
label: impl AsRef<str>,
flags: TableColumnFlags,
width: Option<TableColumnWidth>,
user_id: u32,
) {
self.table_setup_column_with_indent(label, flags, width, None, user_id);
}
pub fn table_setup_column_with_indent(
&self,
label: impl AsRef<str>,
flags: TableColumnFlags,
width: Option<TableColumnWidth>,
indent: Option<TableColumnIndent>,
user_id: u32,
) {
let label_ptr = self.scratch_txt(label);
let raw_flags = flags.bits()
| width.map_or(0, TableColumnWidth::raw_flags)
| indent.map_or(0, TableColumnIndent::raw_flags);
let init_width_or_weight = width.map_or(0.0, TableColumnWidth::value);
unsafe {
sys::igTableSetupColumn(label_ptr, raw_flags, init_width_or_weight, user_id);
}
}
pub fn table_setup_column_fixed_width(
&self,
label: impl AsRef<str>,
flags: TableColumnFlags,
width: f32,
user_id: u32,
) {
self.table_setup_column(label, flags, Some(TableColumnWidth::Fixed(width)), user_id);
}
pub fn table_setup_column_stretch_weight(
&self,
label: impl AsRef<str>,
flags: TableColumnFlags,
weight: f32,
user_id: u32,
) {
self.table_setup_column(
label,
flags,
Some(TableColumnWidth::Stretch(weight)),
user_id,
);
}
pub fn table_headers_row(&self) {
unsafe {
sys::igTableHeadersRow();
}
}
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_next_row(&self) {
self.table_next_row_with_flags(TableRowFlags::NONE, 0.0);
}
pub fn table_next_row_with_flags(&self, flags: TableRowFlags, min_row_height: f32) {
unsafe {
sys::igTableNextRow(flags.bits(), min_row_height);
}
}
#[doc(alias = "TableSetupScrollFreeze")]
pub fn table_setup_scroll_freeze(&self, frozen_cols: i32, frozen_rows: i32) {
unsafe { sys::igTableSetupScrollFreeze(frozen_cols, frozen_rows) }
}
#[doc(alias = "TableHeader")]
pub fn table_header(&self, label: impl AsRef<str>) {
let label_ptr = self.scratch_txt(label);
unsafe { sys::igTableHeader(label_ptr) }
}
#[doc(alias = "TableGetColumnCount")]
pub fn table_get_column_count(&self) -> i32 {
unsafe { sys::igTableGetColumnCount() }
}
#[doc(alias = "TableGetColumnIndex")]
pub fn table_get_column_index(&self) -> i32 {
unsafe { sys::igTableGetColumnIndex() }
}
#[doc(alias = "TableGetRowIndex")]
pub fn table_get_row_index(&self) -> i32 {
unsafe { sys::igTableGetRowIndex() }
}
#[doc(alias = "TableGetColumnName")]
pub fn table_get_column_name(&self, column_n: i32) -> &str {
unsafe {
let ptr = sys::igTableGetColumnName_Int(column_n);
if ptr.is_null() {
""
} else {
CStr::from_ptr(ptr).to_str().unwrap_or("")
}
}
}
#[doc(alias = "TableGetColumnFlags")]
pub fn table_get_column_flags(&self, column_n: i32) -> TableColumnStateFlags {
unsafe { TableColumnStateFlags::from_bits_truncate(sys::igTableGetColumnFlags(column_n)) }
}
#[doc(alias = "TableSetColumnEnabled")]
pub fn table_set_column_enabled(&self, column_n: i32, enabled: bool) {
unsafe { sys::igTableSetColumnEnabled(column_n, enabled) }
}
#[doc(alias = "TableGetHoveredColumn")]
pub fn table_get_hovered_column(&self) -> i32 {
unsafe { sys::igTableGetHoveredColumn() }
}
#[doc(alias = "TableSetColumnWidth")]
pub fn table_set_column_width(&self, column_n: i32, width: f32) {
unsafe { sys::igTableSetColumnWidth(column_n, width) }
}
#[doc(alias = "TableSetBgColor")]
pub fn table_set_bg_color_u32(&self, target: TableBgTarget, color: u32, column_n: i32) {
unsafe { sys::igTableSetBgColor(target as i32, color, column_n) }
}
pub fn table_set_bg_color(&self, target: TableBgTarget, rgba: [f32; 4], column_n: i32) {
let col = crate::colors::Color::from_array(rgba).to_imgui_u32();
unsafe { sys::igTableSetBgColor(target as i32, col, column_n) }
}
#[doc(alias = "TableGetHoveredRow")]
pub fn table_get_hovered_row(&self) -> i32 {
unsafe { sys::igTableGetHoveredRow() }
}
#[doc(alias = "TableGetHeaderRowHeight")]
pub fn table_get_header_row_height(&self) -> f32 {
unsafe { sys::igTableGetHeaderRowHeight() }
}
#[doc(alias = "TableSetColumnSortDirection")]
pub fn table_set_column_sort_direction(
&self,
column_n: i32,
dir: SortDirection,
append_to_sort_specs: bool,
) {
unsafe { sys::igTableSetColumnSortDirection(column_n, dir.into(), append_to_sort_specs) }
}
#[doc(alias = "TableGetSortSpecs")]
pub fn table_get_sort_specs(&self) -> Option<TableSortSpecs<'_>> {
unsafe {
let ptr = sys::igTableGetSortSpecs();
if ptr.is_null() {
None
} else {
Some(TableSortSpecs::from_raw(ptr))
}
}
}
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TableRowFlags: i32 {
const NONE = 0;
const HEADERS = sys::ImGuiTableRowFlags_Headers as i32;
}
}
#[cfg(feature = "serde")]
impl Serialize for TableRowFlags {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32(self.bits())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for TableRowFlags {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bits = i32::deserialize(deserializer)?;
Ok(TableRowFlags::from_bits_truncate(bits))
}
}
#[repr(i32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TableBgTarget {
None = sys::ImGuiTableBgTarget_None as i32,
RowBg0 = sys::ImGuiTableBgTarget_RowBg0 as i32,
RowBg1 = sys::ImGuiTableBgTarget_RowBg1 as i32,
CellBg = sys::ImGuiTableBgTarget_CellBg as i32,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SortDirection {
None = sys::ImGuiSortDirection_None as u8,
Ascending = sys::ImGuiSortDirection_Ascending as u8,
Descending = sys::ImGuiSortDirection_Descending as u8,
}
impl From<SortDirection> for sys::ImGuiSortDirection {
#[inline]
fn from(value: SortDirection) -> sys::ImGuiSortDirection {
match value {
SortDirection::None => sys::ImGuiSortDirection_None,
SortDirection::Ascending => sys::ImGuiSortDirection_Ascending,
SortDirection::Descending => sys::ImGuiSortDirection_Descending,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct TableColumnSortSpec {
pub column_user_id: u32,
pub column_index: i16,
pub sort_order: i16,
pub sort_direction: SortDirection,
}
pub struct TableSortSpecs<'a> {
raw: *mut sys::ImGuiTableSortSpecs,
_marker: std::marker::PhantomData<&'a Ui>,
}
impl<'a> TableSortSpecs<'a> {
pub(crate) unsafe fn from_raw(raw: *mut sys::ImGuiTableSortSpecs) -> Self {
Self {
raw,
_marker: std::marker::PhantomData,
}
}
pub fn is_dirty(&self) -> bool {
unsafe { (*self.raw).SpecsDirty }
}
pub fn clear_dirty(&mut self) {
unsafe { (*self.raw).SpecsDirty = false }
}
pub fn len(&self) -> usize {
unsafe { (*self.raw).SpecsCount as usize }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> TableSortSpecsIter<'_> {
TableSortSpecsIter {
specs: self,
index: 0,
}
}
}
pub struct TableSortSpecsIter<'a> {
specs: &'a TableSortSpecs<'a>,
index: usize,
}
impl<'a> Iterator for TableSortSpecsIter<'a> {
type Item = TableColumnSortSpec;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.specs.len() {
return None;
}
unsafe {
let ptr = (*self.specs.raw).Specs;
if ptr.is_null() {
return None;
}
let spec = &*ptr.add(self.index);
self.index += 1;
let d = spec.SortDirection as u8;
let dir = if d == sys::ImGuiSortDirection_None as u8 {
SortDirection::None
} else if d == sys::ImGuiSortDirection_Ascending as u8 {
SortDirection::Ascending
} else if d == sys::ImGuiSortDirection_Descending as u8 {
SortDirection::Descending
} else {
SortDirection::None
};
Some(TableColumnSortSpec {
column_user_id: spec.ColumnUserID,
column_index: spec.ColumnIndex,
sort_order: spec.SortOrder,
sort_direction: dir,
})
}
}
}
#[must_use]
pub struct TableToken<'ui> {
_ui: &'ui Ui,
}
impl<'ui> TableToken<'ui> {
fn new(ui: &'ui Ui) -> Self {
TableToken { _ui: ui }
}
pub fn end(self) {
}
}
impl<'ui> Drop for TableToken<'ui> {
fn drop(&mut self) {
unsafe {
sys::igEndTable();
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TableHeaderData {
pub index: i16,
pub text_color: ImColor32,
pub bg_color0: ImColor32,
pub bg_color1: ImColor32,
}
impl TableHeaderData {
pub fn new(
index: i16,
text_color: ImColor32,
bg_color0: ImColor32,
bg_color1: ImColor32,
) -> Self {
Self {
index,
text_color,
bg_color0,
bg_color1,
}
}
}
impl Ui {
#[doc(alias = "TableGetHeaderAngledMaxLabelWidth")]
pub fn table_get_header_angled_max_label_width(&self) -> f32 {
unsafe { sys::igTableGetHeaderAngledMaxLabelWidth() }
}
#[doc(alias = "TableAngledHeadersRow")]
pub fn table_angled_headers_row(&self) {
unsafe { sys::igTableAngledHeadersRow() }
}
pub fn table_angled_headers_row_ex_with_data(
&self,
row_id: u32,
angle: f32,
max_label_width: f32,
headers: &[TableHeaderData],
) {
if headers.is_empty() {
unsafe { sys::igTableAngledHeadersRow() }
return;
}
let count = match i32::try_from(headers.len()) {
Ok(n) => n,
Err(_) => return,
};
let mut data: Vec<sys::ImGuiTableHeaderData> = Vec::with_capacity(headers.len());
for h in headers {
data.push(sys::ImGuiTableHeaderData {
Index: h.index as sys::ImGuiTableColumnIdx,
TextColor: u32::from(h.text_color),
BgColor0: u32::from(h.bg_color0),
BgColor1: u32::from(h.bg_color1),
});
}
unsafe {
sys::igTableAngledHeadersRowEx(row_id, angle, max_label_width, data.as_ptr(), count);
}
}
#[doc(alias = "TablePushBackgroundChannel")]
pub fn table_push_background_channel(&self) {
unsafe { sys::igTablePushBackgroundChannel() }
}
#[doc(alias = "TablePopBackgroundChannel")]
pub fn table_pop_background_channel(&self) {
unsafe { sys::igTablePopBackgroundChannel() }
}
#[doc(alias = "TablePushColumnChannel")]
pub fn table_push_column_channel(&self, column_n: i32) {
unsafe { sys::igTablePushColumnChannel(column_n) }
}
#[doc(alias = "TablePopColumnChannel")]
pub fn table_pop_column_channel(&self) {
unsafe { sys::igTablePopColumnChannel() }
}
pub fn with_table_background_channel<R>(&self, f: impl FnOnce() -> R) -> R {
self.table_push_background_channel();
let result = f();
self.table_pop_background_channel();
result
}
pub fn with_table_column_channel<R>(&self, column_n: i32, f: impl FnOnce() -> R) -> R {
self.table_push_column_channel(column_n);
let result = f();
self.table_pop_column_channel();
result
}
#[doc(alias = "TableOpenContextMenu")]
pub fn table_open_context_menu(&self, column_n: Option<i32>) {
unsafe { sys::igTableOpenContextMenu(column_n.unwrap_or(-1)) }
}
}
#[derive(Debug)]
pub struct TableBuilder<'ui> {
ui: &'ui Ui,
id: Cow<'ui, str>,
flags: TableFlags,
sizing_policy: Option<TableSizingPolicy>,
outer_size: [f32; 2],
inner_width: f32,
columns: Vec<TableColumnSetup<Cow<'ui, str>>>,
use_headers: bool,
freeze: Option<(i32, i32)>,
}
impl<'ui> TableBuilder<'ui> {
pub fn new(ui: &'ui Ui, str_id: impl Into<Cow<'ui, str>>) -> Self {
Self {
ui,
id: str_id.into(),
flags: TableFlags::NONE,
sizing_policy: None,
outer_size: [0.0, 0.0],
inner_width: 0.0,
columns: Vec::new(),
use_headers: false,
freeze: None,
}
}
pub fn flags(mut self, flags: TableFlags) -> Self {
self.flags = flags;
self
}
pub fn sizing_policy(mut self, policy: TableSizingPolicy) -> Self {
self.sizing_policy = Some(policy);
self
}
pub fn outer_size(mut self, size: [f32; 2]) -> Self {
self.outer_size = size;
self
}
pub fn inner_width(mut self, width: f32) -> Self {
self.inner_width = width;
self
}
pub fn freeze(mut self, frozen_cols: i32, frozen_rows: i32) -> Self {
self.freeze = Some((frozen_cols, frozen_rows));
self
}
pub fn column(self, name: impl Into<Cow<'ui, str>>) -> ColumnBuilder<'ui> {
ColumnBuilder::new(self, name)
}
pub fn columns<Name: Into<Cow<'ui, str>>>(
mut self,
cols: impl IntoIterator<Item = TableColumnSetup<Name>>,
) -> Self {
self.columns.clear();
for c in cols {
self.columns.push(TableColumnSetup {
name: c.name.into(),
flags: c.flags,
width: c.width,
indent: c.indent,
user_id: c.user_id,
});
}
self
}
pub fn add_column<Name: Into<Cow<'ui, str>>>(mut self, col: TableColumnSetup<Name>) -> Self {
self.columns.push(TableColumnSetup {
name: col.name.into(),
flags: col.flags,
width: col.width,
indent: col.indent,
user_id: col.user_id,
});
self
}
pub fn headers(mut self, enabled: bool) -> Self {
self.use_headers = enabled;
self
}
pub fn build(self, f: impl FnOnce(&Ui)) {
let mut options = TableOptions::from(self.flags);
if let Some(policy) = self.sizing_policy {
options = options.sizing_policy(policy);
}
let Some(token) = self.ui.begin_table_with_sizing(
self.id.as_ref(),
self.columns.len(),
options,
self.outer_size,
self.inner_width,
) else {
return;
};
if let Some((fc, fr)) = self.freeze {
self.ui.table_setup_scroll_freeze(fc, fr);
}
if !self.columns.is_empty() {
for col in &self.columns {
self.ui.table_setup_column_with_indent(
col.name.as_ref(),
col.flags,
col.width,
col.indent,
col.user_id,
);
}
if self.use_headers {
self.ui.table_headers_row();
}
}
f(self.ui);
token.end();
}
}
#[derive(Debug)]
pub struct ColumnBuilder<'ui> {
parent: TableBuilder<'ui>,
name: Cow<'ui, str>,
flags: TableColumnFlags,
width: Option<TableColumnWidth>,
indent: Option<TableColumnIndent>,
user_id: u32,
}
impl<'ui> ColumnBuilder<'ui> {
fn new(parent: TableBuilder<'ui>, name: impl Into<Cow<'ui, str>>) -> Self {
Self {
parent,
name: name.into(),
flags: TableColumnFlags::NONE,
width: None,
indent: None,
user_id: 0,
}
}
pub fn flags(mut self, flags: TableColumnFlags) -> Self {
self.flags = flags;
self
}
pub fn width(mut self, width: f32) -> Self {
self.width = Some(TableColumnWidth::Fixed(width));
self
}
pub fn weight(mut self, weight: f32) -> Self {
self.width = Some(TableColumnWidth::Stretch(weight));
self
}
pub fn indent(mut self, indent: TableColumnIndent) -> Self {
self.indent = Some(indent);
self
}
pub fn indent_enabled(mut self, enabled: bool) -> Self {
self.indent = Some(if enabled {
TableColumnIndent::Enable
} else {
TableColumnIndent::Disable
});
self
}
pub fn angled_header(mut self, enabled: bool) -> Self {
if enabled {
self.flags.insert(TableColumnFlags::ANGLED_HEADER);
} else {
self.flags.remove(TableColumnFlags::ANGLED_HEADER);
}
self
}
pub fn user_id(mut self, id: u32) -> Self {
self.user_id = id;
self
}
pub fn done(mut self) -> TableBuilder<'ui> {
self.parent.columns.push(TableColumnSetup {
name: self.name,
flags: self.flags,
width: self.width,
indent: self.indent,
user_id: self.user_id,
});
self.parent
}
}