tui: Fire proper list selection messages
This commit is contained in:
parent
a3077d5b15
commit
8e7d53aa9a
|
|
@ -60,9 +60,16 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchBrowser> {
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => self
|
}) => {
|
||||||
.selected_item()
|
let result = self.perform(Cmd::Submit);
|
||||||
.map(|item| Message::Patch(PatchMessage::Show(item.id().to_owned()))),
|
match result {
|
||||||
|
CmdResult::Submit(State::One(StateValue::Usize(selected))) => {
|
||||||
|
let item = self.items().get(selected)?;
|
||||||
|
Some(Message::Patch(PatchMessage::Show(item.id().to_owned())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,9 +90,16 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<IssueBrowser> {
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => self
|
}) => {
|
||||||
.selected_item()
|
let result = self.perform(Cmd::Submit);
|
||||||
.map(|item| Message::Issue(IssueMessage::Show(item.id().to_owned()))),
|
match result {
|
||||||
|
CmdResult::Submit(State::One(StateValue::Usize(selected))) => {
|
||||||
|
let item = self.items().get(selected)?;
|
||||||
|
Some(Message::Issue(IssueMessage::Show(item.id().to_owned())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,12 +256,6 @@ impl TableItem<7> for IssueItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableItem<1> for () {
|
|
||||||
fn row(&self, _theme: &Theme) -> [Cell; 1] {
|
|
||||||
[Cell::default()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_patch_state(state: &PatchState) -> (String, Color) {
|
pub fn format_patch_state(state: &PatchState) -> (String, Color) {
|
||||||
match state {
|
match state {
|
||||||
PatchState::Open { conflicts: _ } => (" ● ".into(), Color::Green),
|
PatchState::Open { conflicts: _ } => (" ● ".into(), Color::Green),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ pub mod common;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
pub mod issue;
|
pub mod issue;
|
||||||
pub mod patch;
|
pub mod patch;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use context::{Shortcut, Shortcuts};
|
||||||
use label::Label;
|
use label::Label;
|
||||||
use list::{Property, PropertyList};
|
use list::{Property, PropertyList};
|
||||||
|
|
||||||
use self::list::{ColumnWidth, TableModel};
|
use self::list::ColumnWidth;
|
||||||
|
|
||||||
use super::Widget;
|
use super::Widget;
|
||||||
|
|
||||||
|
|
@ -37,10 +37,10 @@ pub fn reversable_label(content: &str) -> Widget<Label> {
|
||||||
label(content)
|
label(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn container_header(theme: &Theme, label: Widget<Label>) -> Widget<Header<(), 1>> {
|
pub fn container_header(theme: &Theme, label: Widget<Label>) -> Widget<Header<1>> {
|
||||||
let model = TableModel::new([label], [ColumnWidth::Fixed(100)]);
|
let header = Header::new([label], [ColumnWidth::Fixed(100)], theme.clone());
|
||||||
|
|
||||||
Widget::new(Header::new(model, theme.clone()))
|
Widget::new(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn labeled_container(
|
pub fn labeled_container(
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@ use tuirealm::props::{
|
||||||
AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style, TextModifiers,
|
AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style, TextModifiers,
|
||||||
};
|
};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use tuirealm::tui::widgets::{Block, Row};
|
use tuirealm::tui::widgets::{Block, Cell, Row};
|
||||||
use tuirealm::{Frame, MockComponent, State, StateValue};
|
use tuirealm::{Frame, MockComponent, State, StateValue};
|
||||||
|
|
||||||
use crate::ui::ext::HeaderBlock;
|
use crate::ui::ext::HeaderBlock;
|
||||||
use crate::ui::layout;
|
use crate::ui::layout;
|
||||||
use crate::ui::state::TabState;
|
use crate::ui::state::TabState;
|
||||||
use crate::ui::theme::Theme;
|
use crate::ui::theme::Theme;
|
||||||
use crate::ui::widget::{Widget, WidgetComponent};
|
use crate::ui::widget::{utils, Widget, WidgetComponent};
|
||||||
|
|
||||||
use super::label::Label;
|
use super::label::Label;
|
||||||
use super::list::{Table, TableItem, TableModel};
|
use super::list::ColumnWidth;
|
||||||
|
|
||||||
/// Some user events need to be handled globally (e.g. user presses key `q` to quit
|
/// Some user events need to be handled globally (e.g. user presses key `q` to quit
|
||||||
/// the application). This component can be used in conjunction with SubEventClause
|
/// the application). This component can be used in conjunction with SubEventClause
|
||||||
|
|
@ -128,27 +128,23 @@ impl WidgetComponent for Tabs {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A labeled container header.
|
/// A labeled container header.
|
||||||
pub struct Header<V, const W: usize>
|
pub struct Header<const W: usize> {
|
||||||
where
|
header: [Widget<Label>; W],
|
||||||
V: TableItem<W>,
|
widths: [ColumnWidth; W],
|
||||||
{
|
|
||||||
model: TableModel<V, W>,
|
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, const W: usize> Header<V, W>
|
impl<const W: usize> Header<W> {
|
||||||
where
|
pub fn new(header: [Widget<Label>; W], widths: [ColumnWidth; W], theme: Theme) -> Self {
|
||||||
V: TableItem<W>,
|
Self {
|
||||||
{
|
header,
|
||||||
pub fn new(model: TableModel<V, W>, theme: Theme) -> Self {
|
widths,
|
||||||
Self { model, theme }
|
theme,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, const W: usize> WidgetComponent for Header<V, W>
|
impl<const W: usize> WidgetComponent for Header<W> {
|
||||||
where
|
|
||||||
V: TableItem<W> + Clone,
|
|
||||||
{
|
|
||||||
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
||||||
let display = properties
|
let display = properties
|
||||||
.get_or(Attribute::Display, AttrValue::Flag(true))
|
.get_or(Attribute::Display, AttrValue::Flag(true))
|
||||||
|
|
@ -167,9 +163,18 @@ where
|
||||||
.horizontal_margin(1)
|
.horizontal_margin(1)
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
let widths =
|
let widths = utils::column_widths(area, &self.widths, self.theme.tables.spacing);
|
||||||
Table::<V, W>::widths(area, self.model.widths(), self.theme.tables.spacing);
|
let header: [Cell; W] = self
|
||||||
let header: Row<'_> = Row::new(self.model.header(&self.theme));
|
.header
|
||||||
|
.iter()
|
||||||
|
.map(|label| {
|
||||||
|
let cell: Cell = label.into();
|
||||||
|
cell.style(Style::default().fg(self.theme.colors.default_fg))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let header: Row<'_> = Row::new(header);
|
||||||
|
|
||||||
let table = tuirealm::tui::widgets::Table::new(vec![])
|
let table = tuirealm::tui::widgets::Table::new(vec![])
|
||||||
.column_spacing(self.theme.tables.spacing)
|
.column_spacing(self.theme.tables.spacing)
|
||||||
|
|
@ -189,12 +194,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LabeledContainer {
|
pub struct LabeledContainer {
|
||||||
header: Widget<Header<(), 1>>,
|
header: Widget<Header<1>>,
|
||||||
component: Box<dyn MockComponent>,
|
component: Box<dyn MockComponent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LabeledContainer {
|
impl LabeledContainer {
|
||||||
pub fn new(header: Widget<Header<(), 1>>, component: Box<dyn MockComponent>) -> Self {
|
pub fn new(header: Widget<Header<1>>, component: Box<dyn MockComponent>) -> Self {
|
||||||
Self { header, component }
|
Self { header, component }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ use tuirealm::command::{Cmd, CmdResult};
|
||||||
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style};
|
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use tuirealm::tui::widgets::{Block, Cell, Row, TableState};
|
use tuirealm::tui::widgets::{Block, Cell, Row, TableState};
|
||||||
use tuirealm::{Frame, MockComponent, State};
|
use tuirealm::{Frame, MockComponent, State, StateValue};
|
||||||
|
|
||||||
use crate::ui::layout;
|
use crate::ui::layout;
|
||||||
use crate::ui::theme::Theme;
|
use crate::ui::theme::Theme;
|
||||||
use crate::ui::widget::{Widget, WidgetComponent};
|
use crate::ui::widget::{utils, Widget, WidgetComponent};
|
||||||
|
|
||||||
use super::container::Header;
|
use super::container::Header;
|
||||||
use super::label::Label;
|
use super::label::Label;
|
||||||
|
|
@ -30,69 +30,6 @@ pub enum ColumnWidth {
|
||||||
Grow,
|
Grow,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A generic table model with [`W`] columns.
|
|
||||||
///
|
|
||||||
/// [`V`] needs to implement `TableItem` in order to be displayed by the
|
|
||||||
/// table this model is used in.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TableModel<V, const W: usize>
|
|
||||||
where
|
|
||||||
V: TableItem<W>,
|
|
||||||
{
|
|
||||||
/// The table header.
|
|
||||||
header: [Widget<Label>; W],
|
|
||||||
/// Grow behavior of table columns.
|
|
||||||
widths: [ColumnWidth; W],
|
|
||||||
/// Items hold by this model.
|
|
||||||
items: Vec<V>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V, const W: usize> TableModel<V, W>
|
|
||||||
where
|
|
||||||
V: TableItem<W>,
|
|
||||||
{
|
|
||||||
pub fn new(header: [Widget<Label>; W], widths: [ColumnWidth; W]) -> Self {
|
|
||||||
Self {
|
|
||||||
header,
|
|
||||||
widths,
|
|
||||||
items: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a new row to this model.
|
|
||||||
pub fn push_item(&mut self, item: V) {
|
|
||||||
self.items.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all column widhts defined by this model.
|
|
||||||
pub fn widths(&self) -> &[ColumnWidth; W] {
|
|
||||||
&self.widths
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the item count.
|
|
||||||
pub fn count(&self) -> u16 {
|
|
||||||
self.items.len() as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this model's table header.
|
|
||||||
pub fn header(&self, theme: &Theme) -> [Cell; W] {
|
|
||||||
self.header
|
|
||||||
.iter()
|
|
||||||
.map(|label| {
|
|
||||||
let cell: Cell = label.into();
|
|
||||||
cell.style(Style::default().fg(theme.colors.default_fg))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.try_into()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this model's table rows.
|
|
||||||
pub fn rows(&self, theme: &Theme) -> Vec<[Cell; W]> {
|
|
||||||
self.items.iter().map(|item| item.row(theme)).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A component that displays a labeled property.
|
/// A component that displays a labeled property.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Property {
|
pub struct Property {
|
||||||
|
|
@ -186,7 +123,12 @@ pub struct Table<V, const W: usize>
|
||||||
where
|
where
|
||||||
V: TableItem<W> + Clone,
|
V: TableItem<W> + Clone,
|
||||||
{
|
{
|
||||||
model: TableModel<V, W>,
|
/// Items hold by this model.
|
||||||
|
items: Vec<V>,
|
||||||
|
/// The table header.
|
||||||
|
header: [Widget<Label>; W],
|
||||||
|
/// Grow behavior of table columns.
|
||||||
|
widths: [ColumnWidth; W],
|
||||||
state: TableState,
|
state: TableState,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
@ -201,83 +143,48 @@ where
|
||||||
widths: [ColumnWidth; W],
|
widths: [ColumnWidth; W],
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut model = TableModel::new(header, widths);
|
|
||||||
for item in items {
|
|
||||||
model.push_item(item.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = TableState::default();
|
let mut state = TableState::default();
|
||||||
state.select(Some(0));
|
state.select(Some(0));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
model,
|
items: items.to_vec(),
|
||||||
|
header,
|
||||||
|
widths,
|
||||||
state,
|
state,
|
||||||
theme,
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_previous(&mut self) {
|
fn select_previous(&mut self) -> Option<usize> {
|
||||||
let index = match self.state.selected() {
|
let old_index = self.state.selected();
|
||||||
Some(selected) if selected == 0 => 0,
|
let new_index = match old_index {
|
||||||
Some(selected) => selected.saturating_sub(1),
|
Some(selected) if selected == 0 => Some(0),
|
||||||
None => 0,
|
Some(selected) => Some(selected.saturating_sub(1)),
|
||||||
|
None => Some(0),
|
||||||
};
|
};
|
||||||
self.state.select(Some(index));
|
|
||||||
|
if old_index != new_index {
|
||||||
|
self.state.select(new_index);
|
||||||
|
self.state.selected()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, len: usize) {
|
fn select_next(&mut self, len: usize) -> Option<usize> {
|
||||||
let index = match self.state.selected() {
|
let old_index = self.state.selected();
|
||||||
Some(selected) if selected >= len.saturating_sub(1) => len.saturating_sub(1),
|
let new_index = match old_index {
|
||||||
Some(selected) => selected.saturating_add(1),
|
Some(selected) if selected >= len.saturating_sub(1) => Some(len.saturating_sub(1)),
|
||||||
None => 0,
|
Some(selected) => Some(selected.saturating_add(1)),
|
||||||
|
None => Some(0),
|
||||||
};
|
};
|
||||||
self.state.select(Some(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selection(&self) -> Option<&V> {
|
if old_index != new_index {
|
||||||
self.state
|
self.state.select(new_index);
|
||||||
.selected()
|
self.state.selected()
|
||||||
.and_then(|selected| self.model.items.get(selected))
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
/// Calculates `Constraint::Percentage` for each fixed column width in `widths`,
|
|
||||||
/// taking into account the available width in `area` and the column spacing given by `spacing`.
|
|
||||||
pub fn widths(area: Rect, widths: &[ColumnWidth], spacing: u16) -> Vec<Constraint> {
|
|
||||||
let total_spacing = spacing.saturating_mul(widths.len() as u16);
|
|
||||||
let fixed_width = widths
|
|
||||||
.iter()
|
|
||||||
.fold(0u16, |total, &width| match width {
|
|
||||||
ColumnWidth::Fixed(w) => total + w,
|
|
||||||
ColumnWidth::Grow => total,
|
|
||||||
})
|
|
||||||
.saturating_add(total_spacing);
|
|
||||||
|
|
||||||
let grow_count = widths.iter().fold(0u16, |count, &w| {
|
|
||||||
if w == ColumnWidth::Grow {
|
|
||||||
count + 1
|
|
||||||
} else {
|
|
||||||
count
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let grow_width = area
|
|
||||||
.width
|
|
||||||
.saturating_sub(fixed_width)
|
|
||||||
.checked_div(grow_count)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
widths
|
|
||||||
.iter()
|
|
||||||
.map(|width| match width {
|
|
||||||
ColumnWidth::Fixed(w) => {
|
|
||||||
let p: f64 = *w as f64 / area.width as f64 * 100_f64;
|
|
||||||
Constraint::Percentage(p.ceil() as u16)
|
|
||||||
}
|
|
||||||
ColumnWidth::Grow => {
|
|
||||||
let p: f64 = grow_width as f64 / area.width as f64 * 100_f64;
|
|
||||||
Constraint::Percentage(p.floor() as u16)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,12 +202,11 @@ where
|
||||||
.constraints(vec![Constraint::Length(3), Constraint::Min(1)])
|
.constraints(vec![Constraint::Length(3), Constraint::Min(1)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
let widths = Self::widths(area, self.model.widths(), self.theme.tables.spacing);
|
let widths = utils::column_widths(area, &self.widths, self.theme.tables.spacing);
|
||||||
let rows: Vec<Row<'_>> = self
|
let rows: Vec<Row<'_>> = self
|
||||||
.model
|
.items
|
||||||
.rows(&self.theme)
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cells| Row::new(cells.clone()))
|
.map(|item| Row::new(item.row(&self.theme)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let table = tuirealm::tui::widgets::Table::new(rows)
|
let table = tuirealm::tui::widgets::Table::new(rows)
|
||||||
|
|
@ -314,7 +220,11 @@ where
|
||||||
.column_spacing(self.theme.tables.spacing)
|
.column_spacing(self.theme.tables.spacing)
|
||||||
.widths(&widths);
|
.widths(&widths);
|
||||||
|
|
||||||
let mut header = Widget::new(Header::new(self.model.clone(), self.theme.clone()));
|
let mut header = Widget::new(Header::new(
|
||||||
|
self.header.clone(),
|
||||||
|
self.widths,
|
||||||
|
self.theme.clone(),
|
||||||
|
));
|
||||||
header.view(frame, layout[0]);
|
header.view(frame, layout[0]);
|
||||||
frame.render_stateful_widget(table, layout[1], &mut self.state);
|
frame.render_stateful_widget(table, layout[1], &mut self.state);
|
||||||
}
|
}
|
||||||
|
|
@ -325,17 +235,19 @@ where
|
||||||
|
|
||||||
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||||
use tuirealm::command::Direction;
|
use tuirealm::command::Direction;
|
||||||
|
|
||||||
let len = self.model.count() as usize;
|
|
||||||
match cmd {
|
match cmd {
|
||||||
Cmd::Move(Direction::Up) => {
|
Cmd::Move(Direction::Up) => match self.select_previous() {
|
||||||
self.select_previous();
|
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||||
CmdResult::None
|
None => CmdResult::None,
|
||||||
}
|
},
|
||||||
Cmd::Move(Direction::Down) => {
|
Cmd::Move(Direction::Down) => match self.select_next(self.items.len()) {
|
||||||
self.select_next(len);
|
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||||
CmdResult::None
|
None => CmdResult::None,
|
||||||
}
|
},
|
||||||
|
Cmd::Submit => match self.state.selected() {
|
||||||
|
Some(selected) => CmdResult::Submit(State::One(StateValue::Usize(selected))),
|
||||||
|
None => CmdResult::None,
|
||||||
|
},
|
||||||
_ => CmdResult::None,
|
_ => CmdResult::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ impl WidgetComponent for Dashboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IssueBrowser {
|
pub struct IssueBrowser {
|
||||||
|
items: Vec<IssueItem>,
|
||||||
table: Widget<Table<IssueItem, 7>>,
|
table: Widget<Table<IssueItem, 7>>,
|
||||||
shortcuts: Widget<Shortcuts>,
|
shortcuts: Widget<Shortcuts>,
|
||||||
}
|
}
|
||||||
|
|
@ -92,11 +93,15 @@ impl IssueBrowser {
|
||||||
let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
|
let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
|
||||||
.highlight(theme.colors.item_list_highlighted_bg);
|
.highlight(theme.colors.item_list_highlighted_bg);
|
||||||
|
|
||||||
Self { table, shortcuts }
|
Self {
|
||||||
|
items,
|
||||||
|
table,
|
||||||
|
shortcuts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_item(&self) -> Option<&IssueItem> {
|
pub fn items(&self) -> &Vec<IssueItem> {
|
||||||
self.table.selection()
|
&self.items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +128,7 @@ impl WidgetComponent for IssueBrowser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PatchBrowser {
|
pub struct PatchBrowser {
|
||||||
|
items: Vec<PatchItem>,
|
||||||
table: Widget<Table<PatchItem, 8>>,
|
table: Widget<Table<PatchItem, 8>>,
|
||||||
shortcuts: Widget<Shortcuts>,
|
shortcuts: Widget<Shortcuts>,
|
||||||
}
|
}
|
||||||
|
|
@ -168,11 +174,15 @@ impl PatchBrowser {
|
||||||
let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
|
let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
|
||||||
.highlight(theme.colors.item_list_highlighted_bg);
|
.highlight(theme.colors.item_list_highlighted_bg);
|
||||||
|
|
||||||
Self { table, shortcuts }
|
Self {
|
||||||
|
items,
|
||||||
|
table,
|
||||||
|
shortcuts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_item(&self) -> Option<&PatchItem> {
|
pub fn items(&self) -> &Vec<PatchItem> {
|
||||||
self.table.selection()
|
&self.items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
use tuirealm::tui::layout::{Constraint, Rect};
|
||||||
|
|
||||||
|
use super::common::list::ColumnWidth;
|
||||||
|
|
||||||
|
/// Calculates `Constraint::Percentage` for each fixed column width in `widths`,
|
||||||
|
/// taking into account the available width in `area` and the column spacing given by `spacing`.
|
||||||
|
pub fn column_widths(area: Rect, widths: &[ColumnWidth], spacing: u16) -> Vec<Constraint> {
|
||||||
|
let total_spacing = spacing.saturating_mul(widths.len() as u16);
|
||||||
|
let fixed_width = widths
|
||||||
|
.iter()
|
||||||
|
.fold(0u16, |total, &width| match width {
|
||||||
|
ColumnWidth::Fixed(w) => total + w,
|
||||||
|
ColumnWidth::Grow => total,
|
||||||
|
})
|
||||||
|
.saturating_add(total_spacing);
|
||||||
|
|
||||||
|
let grow_count = widths.iter().fold(0u16, |count, &w| {
|
||||||
|
if w == ColumnWidth::Grow {
|
||||||
|
count + 1
|
||||||
|
} else {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let grow_width = area
|
||||||
|
.width
|
||||||
|
.saturating_sub(fixed_width)
|
||||||
|
.checked_div(grow_count)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
widths
|
||||||
|
.iter()
|
||||||
|
.map(|width| match width {
|
||||||
|
ColumnWidth::Fixed(w) => {
|
||||||
|
let p: f64 = *w as f64 / area.width as f64 * 100_f64;
|
||||||
|
Constraint::Percentage(p.ceil() as u16)
|
||||||
|
}
|
||||||
|
ColumnWidth::Grow => {
|
||||||
|
let p: f64 = grow_width as f64 / area.width as f64 * 100_f64;
|
||||||
|
Constraint::Percentage(p.floor() as u16)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue