Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Add flag to hide input bar.
- Adaptive height config option.
- Quit on outer region click.
- Marquee-like animation for the itmes that do not fit the window.

## Changes

Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ struct ListItems {
margin: Margin,
#[def = "false"]
hide_actions: bool,
#[def = "false"]
hide_marquee: bool,
#[def = "60.0"]
action_left_margin: f32,
#[def = "2.0"]
Expand Down
1 change: 1 addition & 0 deletions src/config/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl<'a> From<&'a Config> for ListParams {
.map(Icon::new),
margin: config.list_items.margin.clone(),
hide_actions: config.list_items.hide_actions,
hide_marquee: config.list_items.hide_marquee,
action_left_margin: config.list_items.action_left_margin,
item_spacing: config.list_items.item_spacing,
icon_spacing: config.list_items.icon_spacing,
Expand Down
24 changes: 13 additions & 11 deletions src/draw.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::f32::consts;

use oneshot::Sender;
pub use raqote::Point;
use raqote::{DrawOptions, Path, PathBuilder, Source, StrokeStyle};

pub use background::Params as BgParams;
pub use input_text::Params as InputTextParams;
pub use list_view::{ListItem, Params as ListParams};

use crate::feedback::{self, Feedback, FeedbackListener};
use crate::{style::Radius, Color};

pub type DrawTarget<'a> = raqote::DrawTarget<&'a mut [u32]>;
Expand Down Expand Up @@ -36,8 +36,8 @@ pub enum Widget<'a, It = std::iter::Empty<ListItem<'a>>> {

pub struct Drawables<'a> {
counter: u32,
tx: Option<oneshot::Sender<usize>>,
rx: Option<oneshot::Receiver<usize>>,
feedback: Option<Feedback>,
listener: Option<FeedbackListener>,
state: &'a mut crate::state::State,
background_config: BgParams,
input_config: InputTextParams<'a>,
Expand All @@ -55,12 +55,12 @@ impl<'a> Drawables<'a> {
self.state.skip_offset(),
self.state.selected_item(),
self.state.selected_has_subitems(),
self.tx.take().unwrap(),
self.feedback.take().unwrap(),
self.state.marquee_scroll_offset(),
&self.list_config,
),
4 => {
self.state
.update_skip_offset(self.rx.take().unwrap().recv().unwrap());
self.listener.take().unwrap().apply(self.state);
return None;
}
_ => return None,
Expand Down Expand Up @@ -94,12 +94,12 @@ pub fn make_drawables<'c: 'it, 's: 'it, 'it>(
None
};

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

background_config,
Expand All @@ -120,15 +120,17 @@ impl<'a, It> Widget<'a, It> {
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
tx: Sender<usize>,
feedback: Feedback,
marquee_scroll_offset: f32,
params: &'a ListParams,
) -> Self {
Self::ListView(list_view::ListView::new(
items,
skip_offset,
selected_item,
has_subitems,
tx,
feedback,
marquee_scroll_offset,
params,
))
}
Expand Down
57 changes: 49 additions & 8 deletions src/draw/list_view.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use std::marker::PhantomData;

use oneshot::Sender;
use raqote::{AntialiasMode, DrawOptions, Image, Point};
use raqote::{AntialiasMode, DrawOptions, Image, IntPoint, IntRect, Point};

use super::{DrawTarget, Drawable, Space};
use crate::feedback::Feedback;
use crate::font::{Font, FontBackend, FontColor};
use crate::state::ContinuousMatch;
use crate::style::Margin;
use crate::Color;
use unicode_segmentation::UnicodeSegmentation;

// Padding past the measured text width so the shifted end-point stays past the last glyph.
const MARQUEE_DRAW_PADDING: f32 = 100.0;

pub struct Params {
pub font: Font,
pub font_size: u16,
Expand All @@ -20,6 +23,7 @@ pub struct Params {
pub fallback_icon: Option<crate::icon::Icon>,
pub margin: Margin,
pub hide_actions: bool,
pub hide_marquee: bool,
pub action_left_margin: f32,
pub item_spacing: f32,
pub icon_spacing: f32,
Expand All @@ -37,7 +41,8 @@ pub struct ListView<'a, It> {
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
new_skip: Sender<usize>,
feedback: Feedback,
marquee_scroll_offset: f32,
params: &'a Params,
_tparam: PhantomData<&'a ()>,
}
Expand Down Expand Up @@ -79,15 +84,17 @@ impl<'a, It> ListView<'a, It> {
skip_offset: usize,
selected_item: usize,
has_subitems: bool,
new_skip: Sender<usize>,
feedback: Feedback,
marquee_scroll_offset: f32,
params: &'a Params,
) -> Self {
Self {
items,
skip_offset,
selected_item,
has_subitems,
new_skip,
feedback,
marquee_scroll_offset,
params,
_tparam: PhantomData,
}
Expand All @@ -98,7 +105,7 @@ impl<'a, It> Drawable for ListView<'a, It>
where
It: Iterator<Item = ListItem<'a>>,
{
fn draw(self, dt: &mut DrawTarget<'_>, scale: u16, space: Space, point: Point) -> Space {
fn draw(mut self, dt: &mut DrawTarget<'_>, scale: u16, space: Space, point: Point) -> Space {
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);
Expand All @@ -124,7 +131,7 @@ where
(self.selected_item - self.skip_offset, self.skip_offset)
};

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

for (i, item) in self
.items
Expand Down Expand Up @@ -207,7 +214,40 @@ where
};

let font = &self.params.font;
font.draw(dt, item.name, font_size, pos, end_pos, color, &draw_opts);

if i == selected_item && !self.params.hide_marquee {
let available_width = end_pos.x - pos.x;
let text_width = font.measure_text_width(item.name, font_size);

if text_width > available_width {
self.feedback
.report_marquee_overflow(text_width - available_width);

let clip = IntRect::new(
IntPoint::new(pos.x as i32, 0),
IntPoint::new(end_pos.x as i32, dt.height()),
);
dt.push_clip_rect(clip);

let shifted_pos = Point::new(pos.x - self.marquee_scroll_offset, pos.y);
let wide_end = Point::new(pos.x + text_width + MARQUEE_DRAW_PADDING, end_pos.y);
font.draw(
dt,
item.name,
font_size,
shifted_pos,
wide_end,
color,
&draw_opts,
);

dt.pop_clip();
} else {
font.draw(dt, item.name, font_size, pos, end_pos, color, &draw_opts);
}
} else {
font.draw(dt, item.name, font_size, pos, end_pos, color, &draw_opts);
}

if i == selected_item && has_subname {
if let Some(subname) = item.subname {
Expand Down Expand Up @@ -258,6 +298,7 @@ mod tests {
right: 3.,
},
hide_actions: false,
hide_marquee: false,
action_left_margin: 0.,
item_spacing: 1.5,
icon_spacing: 0.5,
Expand Down
57 changes: 57 additions & 0 deletions src/feedback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::state::State;

struct FeedbackData {
skip_offset: usize,
marquee_overflow: f32,
}

pub struct Feedback {
skip_offset: usize,
marquee_overflow: f32,
tx: Option<oneshot::Sender<FeedbackData>>,
}

impl Feedback {
pub fn report_skip_offset(&mut self, skip_offset: usize) {
self.skip_offset = skip_offset;
}

pub fn report_marquee_overflow(&mut self, overflow: f32) {
self.marquee_overflow = overflow;
}
}

impl Drop for Feedback {
fn drop(&mut self) {
if let Some(tx) = self.tx.take() {
let _ = tx.send(FeedbackData {
skip_offset: self.skip_offset,
marquee_overflow: self.marquee_overflow,
});
}
}
}

pub struct FeedbackListener {
rx: oneshot::Receiver<FeedbackData>,
}

impl FeedbackListener {
pub fn apply(self, state: &mut State) {
let data = self.rx.recv().unwrap();
state.update_skip_offset(data.skip_offset);
state.set_marquee_overflow(data.marquee_overflow);
}
}

pub fn channel() -> (Feedback, FeedbackListener) {
let (tx, rx) = oneshot::channel();
(
Feedback {
skip_offset: 0,
marquee_overflow: 0.0,
tx: Some(tx),
},
FeedbackListener { rx },
)
}
2 changes: 2 additions & 0 deletions src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub trait FontBackend: Sized {

fn font_by_path(path: &Path) -> Result<Self>;

fn measure_text_width(&self, text: &str, font_size: f32) -> f32;

#[allow(clippy::too_many_arguments)]
fn draw(
&self,
Expand Down
18 changes: 18 additions & 0 deletions src/font/fdue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,24 @@ impl FontBackend for Font {
Font::from_path(path, None)
}

fn measure_text_width(&self, text: &str, font_size: f32) -> f32 {
let mut layout = self.layout.borrow_mut();
layout.reset(&LayoutSettings {
x: 0.0,
y: 0.0,
max_height: Some(font_size),
vertical_align: VerticalAlign::Middle,
wrap_style: WrapStyle::Letter,
..LayoutSettings::default()
});
layout.append(&[&self.inner], &TextStyle::new(text, font_size, 0));
layout
.glyphs()
.last()
.map(|g| g.x + g.width as f32)
.unwrap_or(0.0)
}

fn draw(
&self,
dt: &mut DrawTarget,
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub(crate) use draw::DrawTarget;
mod color;
mod draw;
mod exec;
mod feedback;
mod font;
mod icon;
mod input_parser;
Expand All @@ -24,6 +25,7 @@ macro_rules! prog_name {
};
}

#[derive(Clone, Copy)]
pub struct Viewport {
buf_size: (i32, i32),
content_offset: (f32, f32),
Expand Down
Loading
Loading