Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

- Support some actions with pointer (e.g mouse).
- Add flag to hide input bar.
- Adaptive height config option.

## Changes

- Log to stderr instead of stdout.
- Prefer earlier match with same score for input search.
- Empty subitems now hidden.

## Fixes

Expand Down
5 changes: 5 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct Config {
#[def = "false"]
force_window: bool,
window_offsets: Option<(i32, i32)>,
adaptive_height: bool,
scale: Option<u16>,
term: Option<String>,
font: Option<String>,
Expand Down Expand Up @@ -63,6 +64,10 @@ impl Config {
pub fn override_password(&mut self) {
self.input_text.password = true;
}

pub fn is_height_adaptive(&self) -> bool {
self.adaptive_height
}
}

#[derive(Defaults, Deserialize)]
Expand Down
48 changes: 35 additions & 13 deletions src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl<'a> Drawables<'a> {
self.state.processed_entries(),
self.state.skip_offset(),
self.state.selected_item(),
self.state.has_subitems(),
self.tx.take().unwrap(),
&self.list_config,
),
Expand All @@ -70,24 +71,43 @@ impl<'a> Drawables<'a> {
pub fn make_drawables<'c: 'it, 's: 'it, 'it>(
config: &'c crate::config::Config,
state: &'s mut crate::state::State,
) -> Drawables<'it> {
scale: u16,
) -> (Drawables<'it>, Option<Space>) {
let background_config = config.param();
let input_config = config.param();
let list_config = config.param();
let input_config: InputTextParams<'_> = config.param();
let list_config: ListParams = config.param();

state.process_entries();

let space = if config.is_height_adaptive() {
let input_space = input_config.occupied_space(scale);
let list_space = list_config.space_for_entries(
state.processed_entries().len().max(1),
scale,
state.has_subitems(),
);
Some(Space {
width: 0.,
height: input_space.height + list_space.height,
})
} else {
None
};

let (tx, rx) = oneshot::channel();
Drawables {
counter: 0,
tx: Some(tx),
rx: Some(rx),
state,

background_config,
input_config,
list_config,
}
(
Drawables {
counter: 0,
tx: Some(tx),
rx: Some(rx),
state,

background_config,
input_config,
list_config,
},
space,
)
}

impl<'a, It> Widget<'a, It> {
Expand All @@ -99,13 +119,15 @@ impl<'a, It> Widget<'a, It> {
items: It,
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
tx: Sender<usize>,
params: &'a ListParams,
) -> Self {
Self::ListView(list_view::ListView::new(
items,
skip_offset,
selected_item,
has_subitems,
tx,
params,
))
Expand Down
32 changes: 28 additions & 4 deletions src/draw/input_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::font::{Font, FontBackend, FontColor};
use crate::style::{Margin, Padding, Radius};
use crate::Color;

const MIN_PADDING_TOP: f32 = 2.0;
const MIN_PADDING_BOTTOM: f32 = 5.0;

pub struct Params<'a> {
pub hide: bool,
pub font: Font,
Expand All @@ -25,6 +28,29 @@ pub struct InputText<'a> {
rect: RoundedRect,
}

impl Params<'_> {
pub fn occupied_space(&self, scale: u16) -> Space {
if self.hide {
return Space {
width: 0.,
height: 0.,
};
}

let mut padding = &self.padding * f32::from(scale);
padding.top += MIN_PADDING_TOP;
padding.bottom += MIN_PADDING_BOTTOM;
let margin = &self.margin * f32::from(scale);

let font_size = f32::from(self.font_size * scale);
let rect_height = padding.top + font_size + padding.bottom;
Space {
width: 0.,
height: margin.top + rect_height + margin.bottom,
}
}
}

impl<'a> InputText<'a> {
pub fn new(text: &'a str, params: &'a Params<'a>) -> Self {
let color = params.bg_color;
Expand All @@ -50,10 +76,8 @@ impl<'a> Drawable for InputText<'a> {
let font_size = f32::from(self.params.font_size * scale);

let mut padding = &self.params.padding * f32::from(scale);
const PADDING_TOP: f32 = 2.0;
const PADDING_BOTTOM: f32 = 5.0;
padding.top += PADDING_TOP;
padding.bottom += PADDING_BOTTOM;
padding.top += MIN_PADDING_TOP;
padding.bottom += MIN_PADDING_BOTTOM;
let margin = &self.params.margin * f32::from(scale);

let rect_width = space.width - margin.left - margin.right;
Expand Down
97 changes: 83 additions & 14 deletions src/draw/list_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,57 @@ pub struct ListView<'a, It> {
items: It,
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
new_skip: Sender<usize>,
params: &'a Params,
_tparam: PhantomData<&'a ()>,
}

impl Params {
fn common_bounds(&self, scale: u16) -> (Margin, f32, f32) {
let margin = &self.margin * f32::from(scale);
let item_spacing = self.item_spacing * f32::from(scale);
let icon_size = self.icon_size.unwrap_or(0) * scale;
let font_size = f32::from(self.font_size * scale);
let entry_height = font_size.max(f32::from(icon_size));
(margin, item_spacing, entry_height)
}

pub fn entries_fitted(&self, scale: u16, has_subname: bool, space: Space) -> usize {
let (margin, item_spacing, entry_height) = self.common_bounds(scale);

(((space.height - margin.top - margin.bottom + item_spacing)
/ (entry_height + item_spacing)) as usize)
.saturating_sub(has_subname as usize)
}

pub fn space_for_entries(&self, count: usize, scale: u16, has_subname: bool) -> Space {
let (margin, item_spacing, entry_height) = self.common_bounds(scale);

let has_subname = (has_subname && !self.hide_actions) as usize;
let count = has_subname + count;
let height = count as f32 * entry_height
+ (count - 1 + has_subname) as f32 * item_spacing
+ margin.top
+ margin.bottom;
Space { height, width: 0.0 }
}
}

impl<'a, It> ListView<'a, It> {
pub fn new(
items: It,
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
new_skip: Sender<usize>,
params: &'a Params,
) -> Self {
Self {
items,
skip_offset,
selected_item,
has_subitems,
new_skip,
params,
_tparam: PhantomData,
Expand All @@ -65,28 +99,18 @@ where
It: Iterator<Item = ListItem<'a>>,
{
fn draw(self, dt: &mut DrawTarget<'_>, scale: u16, space: Space, point: Point) -> Space {
let margin = &self.params.margin * f32::from(scale);
let item_spacing = self.params.item_spacing * f32::from(scale);
let (margin, item_spacing, entry_height) = self.params.common_bounds(scale);
let icon_size = self.params.icon_size.unwrap_or(0) * scale;
let icon_spacing = self.params.icon_spacing * f32::from(scale);

let icon_size_f32 = f32::from(icon_size);
let font_size = f32::from(self.params.font_size * scale);
let top_offset = point.y + margin.top + (icon_size_f32 - font_size).max(0.) / 2.;
let entry_height = font_size.max(icon_size_f32);

let mut iter = self.items.peekable();

let hide_actions = self.params.hide_actions;
// For now either all items has subname or none.
let has_subname = iter
.peek()
.map(|e| e.subname.is_some() && !hide_actions)
.unwrap_or(false);
let has_subname = self.has_subitems && !hide_actions;

let displayed_items = ((space.height - margin.top - margin.bottom + item_spacing)
/ (entry_height + item_spacing)) as usize
- has_subname as usize;
let displayed_items = self.params.entries_fitted(scale, has_subname, space);

let max_offset = self.skip_offset + displayed_items;
let (selected_item, skip_offset) = if self.selected_item < self.skip_offset {
Expand All @@ -102,7 +126,12 @@ where

self.new_skip.send(skip_offset).unwrap();

for (i, item) in iter.skip(skip_offset).enumerate().take(displayed_items) {
for (i, item) in self
.items
.skip(skip_offset)
.enumerate()
.take(displayed_items)
{
let relative_offset = (i as f32 + (i > selected_item && has_subname) as i32 as f32)
* (entry_height + item_spacing);
let x_offset = point.x + margin.left;
Expand Down Expand Up @@ -201,3 +230,43 @@ where
space
}
}

#[cfg(test)]
mod tests {
use super::*;

use test_case::test_matrix;

#[test_matrix(
[0, 1, 2, 10, 100],
[1, 2, 3],
[false, true]
)]
fn test_param_entries_fit(count: usize, scale: u16, has_subname: bool) {
let param = Params {
font: crate::font::InnerFont::default().into(),
font_size: 24,
font_color: Color::from_rgba(15, 15, 15, 255),
selected_font_color: Color::from_rgba(15, 15, 15, 255),
match_color: None,
icon_size: Some(16),
fallback_icon: None,
margin: Margin {
top: 10.,
bottom: 5.,
left: 2.,
right: 3.,
},
hide_actions: false,
action_left_margin: 0.,
item_spacing: 1.5,
icon_spacing: 0.5,
};

let space = param.space_for_entries(count, scale, true);
let x = param.entries_fitted(scale, true, space);

dbg!((count, scale, has_subname, space.height, x));
assert_eq!(x, count);
}
}
7 changes: 6 additions & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,12 @@ impl State {
self.selected_item
}

pub fn processed_entries(&self) -> impl Iterator<Item = ListItem<'_>> {
pub fn has_subitems(&self) -> bool {
// For now either all items has subname or none.
self.inner.subentries_len(self.selected_item) > 0
}

pub fn processed_entries(&self) -> impl ExactSizeIterator<Item = ListItem<'_>> {
self.filtered_lines
.list_items(&self.inner, self.selected_item, self.selected_subitem)
}
Expand Down
2 changes: 1 addition & 1 deletion src/state/filtered_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl FilteredLines {
mode: &'m Mode,
item: usize,
subitem: usize,
) -> impl Iterator<Item = ListItem<'_>> + '_ {
) -> impl ExactSizeIterator<Item = ListItem<'_>> + '_ {
match self {
Self(Either::Left(x)) => {
Either::Left(x.iter().enumerate().map(move |(idx, (item_idx, s_match))| {
Expand Down
6 changes: 5 additions & 1 deletion src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,11 @@ impl Window {
};
let mut point = Point::new(0., 0.);

let mut drawables = crate::draw::make_drawables(&self.config, &mut self.state);
let (mut drawables, dyn_space) =
crate::draw::make_drawables(&self.config, &mut self.state, self.scale);
if let Some(dyn_space) = dyn_space {
space_left.height = space_left.height.min(dyn_space.height);
}
while let Some(d) = drawables.borrowed_next() {
let occupied = d.draw(&mut dt, self.scale, space_left, point);
debug_assert!(
Expand Down
Loading