Tidy up after the sizing rework
The caps model landed in pieces, and the pieces left seams. This joins them up, and fixes two things the reread turned up. Deferred subsurface syncing is gone. `needs_tiles` existed because `select` had no queue handle to sync with, so main.rs grew a bespoke event loop to notice the flag afterwards. The dispatch handlers are handed a handle already: pass it down and let `select` do the work itself. main.rs is back to one uniform `pump`. The layout is built once, in run(), and passed to App::new, rather than built there and again inside it. `display` moves from Settings, which is what App needs, to Options, which is what the caller needs it for. `timeout = 0` meant an immediate deadline, so uncommenting the line in the shipped config would have made wl-pick exit before you saw it. Zero now means no timeout, which is what the comment beside it always claimed. `timeout` was also settable but documented nowhere -- not in --help, not in the README. Both now list every key config.rs accepts. Theme::default's max-width/max-height were placeholder pixel counts that happened to match one monitor. They are i32::MAX now: no cap of their own, with Layout clamping to the display.
This commit is contained in:
@@ -162,6 +162,7 @@ outputs = yes
|
|||||||
live = all
|
live = all
|
||||||
fps = 12
|
fps = 12
|
||||||
format = tsv
|
format = tsv
|
||||||
|
timeout = 0 # seconds; 0 means none
|
||||||
```
|
```
|
||||||
|
|
||||||
Sizes take sway's units: `600px` is absolute, `90ppt` a percentage — and the
|
Sizes take sway's units: `600px` is absolute, `90ppt` a percentage — and the
|
||||||
|
|||||||
+3
-10
@@ -56,12 +56,9 @@ pub struct Settings {
|
|||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub live: Live,
|
pub live: Live,
|
||||||
pub fps: u32,
|
pub fps: u32,
|
||||||
/// Integer scale of the display the overlay renders on.
|
/// Integer scale of the display the overlay renders on, and its name, so
|
||||||
|
/// the overlay maps there rather than wherever the compositor would put it.
|
||||||
pub scale: i32,
|
pub scale: i32,
|
||||||
/// That display's logical size, which the grid is fitted into.
|
|
||||||
pub display: (i32, i32),
|
|
||||||
/// And its name, so the overlay maps there rather than wherever the
|
|
||||||
/// compositor would have put it.
|
|
||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +87,6 @@ pub struct App {
|
|||||||
pub(crate) sel: usize,
|
pub(crate) sel: usize,
|
||||||
/// First row of the grid on screen. The rest scroll.
|
/// First row of the grid on screen. The rest scroll.
|
||||||
pub(crate) scroll: i32,
|
pub(crate) scroll: i32,
|
||||||
/// Set when the viewport moved and the subsurfaces need re-placing.
|
|
||||||
pub(crate) needs_tiles: bool,
|
|
||||||
pub(crate) shift: bool,
|
pub(crate) shift: bool,
|
||||||
|
|
||||||
/// Where the pointer is, and which tile it pressed. Hovering deliberately
|
/// Where the pointer is, and which tile it pressed. Hovering deliberately
|
||||||
@@ -158,16 +153,15 @@ impl App {
|
|||||||
qh: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
targets: Vec<Target>,
|
targets: Vec<Target>,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
layout: Layout,
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
let Settings {
|
let Settings {
|
||||||
theme,
|
theme,
|
||||||
live,
|
live,
|
||||||
fps,
|
fps,
|
||||||
scale,
|
scale,
|
||||||
display,
|
|
||||||
output,
|
output,
|
||||||
} = settings;
|
} = settings;
|
||||||
let layout = Layout::new(&theme, targets.len() as i32, display);
|
|
||||||
// Bind everything up front so a compositor missing a protocol fails
|
// Bind everything up front so a compositor missing a protocol fails
|
||||||
// here, with a name, rather than halfway through a capture.
|
// here, with a name, rather than halfway through a capture.
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
@@ -190,7 +184,6 @@ impl App {
|
|||||||
scale,
|
scale,
|
||||||
sel: 0,
|
sel: 0,
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
needs_tiles: false,
|
|
||||||
shift: false,
|
shift: false,
|
||||||
hover: None,
|
hover: None,
|
||||||
pressed: None,
|
pressed: None,
|
||||||
|
|||||||
+5
-2
@@ -64,6 +64,7 @@ config:
|
|||||||
live = all
|
live = all
|
||||||
fps = 12
|
fps = 12
|
||||||
format = tsv
|
format = tsv
|
||||||
|
timeout = 0 # seconds; 0 means none
|
||||||
|
|
||||||
formats:
|
formats:
|
||||||
|
|
||||||
@@ -118,6 +119,8 @@ pub struct Options {
|
|||||||
pub format: Format,
|
pub format: Format,
|
||||||
pub outputs: bool,
|
pub outputs: bool,
|
||||||
pub timeout: Option<Duration>,
|
pub timeout: Option<Duration>,
|
||||||
|
/// The logical size of the display the grid will be laid out for.
|
||||||
|
pub display: (i32, i32),
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +185,12 @@ impl Args {
|
|||||||
format: self.format.or(cfg.format).unwrap_or(Format::Tsv),
|
format: self.format.or(cfg.format).unwrap_or(Format::Tsv),
|
||||||
outputs: self.outputs.or(cfg.outputs).unwrap_or(true),
|
outputs: self.outputs.or(cfg.outputs).unwrap_or(true),
|
||||||
timeout: self.timeout.or(cfg.timeout),
|
timeout: self.timeout.or(cfg.timeout),
|
||||||
|
display: (display.width, display.height),
|
||||||
settings: Settings {
|
settings: Settings {
|
||||||
theme,
|
theme,
|
||||||
live: self.live.or(cfg.live).unwrap_or(Live::All),
|
live: self.live.or(cfg.live).unwrap_or(Live::All),
|
||||||
fps: self.fps.or(cfg.fps).unwrap_or(12),
|
fps: self.fps.or(cfg.fps).unwrap_or(12),
|
||||||
scale: display.scale,
|
scale: display.scale,
|
||||||
display: (display.width, display.height),
|
|
||||||
output: display.name.clone(),
|
output: display.name.clone(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -226,7 +229,7 @@ pub fn parse_args() -> Result<Args, String> {
|
|||||||
"--timeout" => {
|
"--timeout" => {
|
||||||
let v = it.next().ok_or("--timeout needs seconds")?;
|
let v = it.next().ok_or("--timeout needs seconds")?;
|
||||||
let secs: f64 = v.parse().map_err(|_| format!("bad --timeout: {v}"))?;
|
let secs: f64 = v.parse().map_err(|_| format!("bad --timeout: {v}"))?;
|
||||||
args.timeout = Some(Duration::from_secs_f64(secs));
|
args.timeout = (secs > 0.0).then(|| Duration::from_secs_f64(secs));
|
||||||
}
|
}
|
||||||
"-h" | "--help" => {
|
"-h" | "--help" => {
|
||||||
print!("{HELP}");
|
print!("{HELP}");
|
||||||
|
|||||||
+8
-1
@@ -151,7 +151,12 @@ impl Config {
|
|||||||
"live" => self.live = Some(Live::parse(value)?),
|
"live" => self.live = Some(Live::parse(value)?),
|
||||||
"fps" => self.fps = Some(number(value)?),
|
"fps" => self.fps = Some(number(value)?),
|
||||||
"format" => self.format = Some(Format::parse(value)?),
|
"format" => self.format = Some(Format::parse(value)?),
|
||||||
"timeout" => self.timeout = Some(Duration::from_secs_f64(number(value)?)),
|
// Zero is how you say "no timeout"; an immediate deadline would
|
||||||
|
// only ever be a mistake.
|
||||||
|
"timeout" => {
|
||||||
|
let secs: f64 = number(value)?;
|
||||||
|
self.timeout = (secs > 0.0).then(|| Duration::from_secs_f64(secs));
|
||||||
|
}
|
||||||
other => return Err(format!("unknown setting {other:?}")),
|
other => return Err(format!("unknown setting {other:?}")),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -236,6 +241,7 @@ max-rows = 3
|
|||||||
live = current
|
live = current
|
||||||
fps = 30
|
fps = 30
|
||||||
labels = no
|
labels = no
|
||||||
|
timeout = 0
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.expect("should parse");
|
.expect("should parse");
|
||||||
@@ -247,6 +253,7 @@ labels = no
|
|||||||
assert_eq!(cfg.max_rows, Some(3));
|
assert_eq!(cfg.max_rows, Some(3));
|
||||||
assert_eq!(cfg.fps, Some(30));
|
assert_eq!(cfg.fps, Some(30));
|
||||||
assert_eq!(cfg.labels, Some(false));
|
assert_eq!(cfg.labels, Some(false));
|
||||||
|
assert_eq!(cfg.timeout, None, "zero means no timeout");
|
||||||
assert!(cfg.live.is_some());
|
assert!(cfg.live.is_some());
|
||||||
// Untouched settings stay unset, so defaults survive.
|
// Untouched settings stay unset, so defaults survive.
|
||||||
assert_eq!(cfg.foreground, None);
|
assert_eq!(cfg.foreground, None);
|
||||||
|
|||||||
+10
-19
@@ -57,9 +57,8 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<ExitCode, Box<dyn Error>> {
|
fn run() -> Result<ExitCode, Box<dyn Error>> {
|
||||||
let args = cli::parse_args().map_err(|e| -> Box<dyn Error> { e.into() })?;
|
let args = cli::parse_args().map_err(Box::<dyn Error>::from)?;
|
||||||
let config =
|
let config = Config::load(args.config.as_deref()).map_err(Box::<dyn Error>::from)?;
|
||||||
Config::load(args.config.as_deref()).map_err(|e| -> Box<dyn Error> { e.into() })?;
|
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut phases = Phases::new(args.verbose);
|
let mut phases = Phases::new(args.verbose);
|
||||||
@@ -88,29 +87,29 @@ fn run() -> Result<ExitCode, Box<dyn Error>> {
|
|||||||
phases.mark("sway-tree");
|
phases.mark("sway-tree");
|
||||||
|
|
||||||
cli::arm_timeout(opts.timeout);
|
cli::arm_timeout(opts.timeout);
|
||||||
let settings = opts.settings;
|
let (display, settings) = (opts.display, opts.settings);
|
||||||
let theme = &settings.theme;
|
let theme = &settings.theme;
|
||||||
let scale = settings.scale;
|
let scale = settings.scale;
|
||||||
|
// The grid is measured once here: the label shaping below and the overlay
|
||||||
|
// itself must agree about how wide a label may be.
|
||||||
|
let layout = Layout::new(theme, targets.len() as i32, display);
|
||||||
// Start shaping labels now: it costs ~55ms of font loading and glyph
|
// Start shaping labels now: it costs ~55ms of font loading and glyph
|
||||||
// rasterising, and the captures below are ~55ms of waiting on the
|
// rasterising, and the captures below are ~55ms of waiting on the
|
||||||
// compositor, so the two overlap almost exactly.
|
// compositor, so the two overlap almost exactly.
|
||||||
let labels = theme.labels.then(|| {
|
let labels = layout.label(0, 0).map(|label| {
|
||||||
let layout = Layout::new(theme, targets.len() as i32, settings.display);
|
|
||||||
text::spawn(
|
text::spawn(
|
||||||
targets.iter().map(Target::label).collect(),
|
targets.iter().map(Target::label).collect(),
|
||||||
theme.font.clone(),
|
theme.font.clone(),
|
||||||
theme.font_px * scale as f32,
|
theme.font_px * scale as f32,
|
||||||
(theme.line_h * scale) as f32,
|
(theme.line_h * scale) as f32,
|
||||||
// The label box is a tile wide; with no tiles there is nothing to
|
(label.w * scale) as f32,
|
||||||
// shape anyway.
|
|
||||||
(layout.label(0, 0).map(|r| r.w).unwrap_or(1) * scale) as f32,
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let conn = Connection::connect_to_env()?;
|
let conn = Connection::connect_to_env()?;
|
||||||
let (globals, mut queue) = registry_queue_init::<App>(&conn)?;
|
let (globals, mut queue) = registry_queue_init::<App>(&conn)?;
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
let mut app = App::new(&globals, &qh, targets, settings)?;
|
let mut app = App::new(&globals, &qh, targets, settings, layout)?;
|
||||||
|
|
||||||
// Two roundtrips: one for the toplevel list, one for each handle's state.
|
// Two roundtrips: one for the toplevel list, one for each handle's state.
|
||||||
queue.roundtrip(&mut app)?;
|
queue.roundtrip(&mut app)?;
|
||||||
@@ -141,15 +140,7 @@ fn run() -> Result<ExitCode, Box<dyn Error>> {
|
|||||||
conn.flush()?;
|
conn.flush()?;
|
||||||
phases.mark("mapped");
|
phases.mark("mapped");
|
||||||
|
|
||||||
// Scrolling re-places the subsurfaces; doing it here rather than inside the
|
pump(&mut queue, &mut app, |a| a.finished())?;
|
||||||
// key handler coalesces a held-down arrow into one update per dispatch.
|
|
||||||
while !app.finished() {
|
|
||||||
queue.blocking_dispatch(&mut app)?;
|
|
||||||
if std::mem::take(&mut app.needs_tiles) {
|
|
||||||
app.sync_tiles(&qh);
|
|
||||||
conn.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.verbose {
|
if opts.verbose {
|
||||||
app.report(start.elapsed());
|
app.report(start.elapsed());
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-21
@@ -236,30 +236,31 @@ impl App {
|
|||||||
surface.commit();
|
surface.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_sel(&mut self, delta: i32) {
|
fn move_sel(&mut self, delta: i32, qh: &QueueHandle<Self>) {
|
||||||
let n = self.tiles.len() as i32;
|
let n = self.tiles.len() as i32;
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.select((self.sel as i32 + delta).rem_euclid(n) as usize);
|
self.select((self.sel as i32 + delta).rem_euclid(n) as usize, qh);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_row(&mut self, rows: i32) {
|
fn move_row(&mut self, rows: i32, qh: &QueueHandle<Self>) {
|
||||||
let n = self.tiles.len() as i32;
|
let n = self.tiles.len() as i32;
|
||||||
let target = self.sel as i32 + rows * self.layout.cols;
|
let target = self.sel as i32 + rows * self.layout.cols;
|
||||||
if target >= 0 && target < n {
|
if target >= 0 && target < n {
|
||||||
self.select(target as usize);
|
self.select(target as usize, qh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the selection, scrolling the least that keeps it on screen. Every
|
/// Move the selection, scrolling the least that keeps it on screen. Every
|
||||||
/// keyboard move goes through here, so the selection is never off-view.
|
/// move goes through here, so the selection is never off-view and the
|
||||||
fn select(&mut self, i: usize) {
|
/// subsurfaces always match the viewport.
|
||||||
|
fn select(&mut self, i: usize, qh: &QueueHandle<Self>) {
|
||||||
self.sel = i;
|
self.sel = i;
|
||||||
let scroll = self.layout.reveal(i, self.scroll);
|
let scroll = self.layout.reveal(i, self.scroll);
|
||||||
if scroll != self.scroll {
|
if scroll != self.scroll {
|
||||||
self.scroll = scroll;
|
self.scroll = scroll;
|
||||||
self.needs_tiles = true;
|
self.sync_tiles(qh);
|
||||||
}
|
}
|
||||||
self.paint();
|
self.paint();
|
||||||
}
|
}
|
||||||
@@ -294,7 +295,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key(&mut self, code: u32) {
|
fn key(&mut self, code: u32, qh: &QueueHandle<Self>) {
|
||||||
match code {
|
match code {
|
||||||
KEY_LEFTSHIFT | KEY_RIGHTSHIFT => self.shift = true,
|
KEY_LEFTSHIFT | KEY_RIGHTSHIFT => self.shift = true,
|
||||||
KEY_ESC | KEY_Q => self.ending = Ending::Cancelled,
|
KEY_ESC | KEY_Q => self.ending = Ending::Cancelled,
|
||||||
@@ -302,15 +303,15 @@ impl App {
|
|||||||
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
|
self.picked = self.tiles.get(self.sel).map(|t| t.target.clone());
|
||||||
self.ending = Ending::Picked;
|
self.ending = Ending::Picked;
|
||||||
}
|
}
|
||||||
KEY_TAB if self.shift => self.move_sel(-1),
|
KEY_TAB if self.shift => self.move_sel(-1, qh),
|
||||||
KEY_TAB | KEY_RIGHT | KEY_L => self.move_sel(1),
|
KEY_TAB | KEY_RIGHT | KEY_L => self.move_sel(1, qh),
|
||||||
KEY_LEFT | KEY_H => self.move_sel(-1),
|
KEY_LEFT | KEY_H => self.move_sel(-1, qh),
|
||||||
KEY_DOWN | KEY_J => self.move_row(1),
|
KEY_DOWN | KEY_J => self.move_row(1, qh),
|
||||||
KEY_UP | KEY_K => self.move_row(-1),
|
KEY_UP | KEY_K => self.move_row(-1, qh),
|
||||||
KEY_HOME => self.select(0),
|
KEY_HOME => self.select(0, qh),
|
||||||
KEY_END => self.select(self.tiles.len().saturating_sub(1)),
|
KEY_END => self.select(self.tiles.len().saturating_sub(1), qh),
|
||||||
KEY_PGUP => self.move_row(-self.layout.visible_rows),
|
KEY_PGUP => self.move_row(-self.layout.visible_rows, qh),
|
||||||
KEY_PGDN => self.move_row(self.layout.visible_rows),
|
KEY_PGDN => self.move_row(self.layout.visible_rows, qh),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,11 +374,11 @@ impl Dispatch<WlKeyboard, ()> for App {
|
|||||||
event: wl_keyboard::Event,
|
event: wl_keyboard::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
_: &Connection,
|
_: &Connection,
|
||||||
_: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
if let wl_keyboard::Event::Key { key, state, .. } = event {
|
if let wl_keyboard::Event::Key { key, state, .. } = event {
|
||||||
match state {
|
match state {
|
||||||
WEnum::Value(wl_keyboard::KeyState::Pressed) => app.key(key),
|
WEnum::Value(wl_keyboard::KeyState::Pressed) => app.key(key, qh),
|
||||||
WEnum::Value(wl_keyboard::KeyState::Released)
|
WEnum::Value(wl_keyboard::KeyState::Released)
|
||||||
if key == KEY_LEFTSHIFT || key == KEY_RIGHTSHIFT =>
|
if key == KEY_LEFTSHIFT || key == KEY_RIGHTSHIFT =>
|
||||||
{
|
{
|
||||||
@@ -399,7 +400,7 @@ impl Dispatch<WlPointer, ()> for App {
|
|||||||
event: wl_pointer::Event,
|
event: wl_pointer::Event,
|
||||||
_: &(),
|
_: &(),
|
||||||
_: &Connection,
|
_: &Connection,
|
||||||
_: &QueueHandle<Self>,
|
qh: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
wl_pointer::Event::Enter {
|
wl_pointer::Event::Enter {
|
||||||
@@ -437,7 +438,9 @@ impl Dispatch<WlPointer, ()> for App {
|
|||||||
state: WEnum::Value(state),
|
state: WEnum::Value(state),
|
||||||
..
|
..
|
||||||
} => app.click(state == wl_pointer::ButtonState::Pressed),
|
} => app.click(state == wl_pointer::ButtonState::Pressed),
|
||||||
wl_pointer::Event::Axis { value, .. } => app.move_sel(if value > 0.0 { 1 } else { -1 }),
|
wl_pointer::Event::Axis { value, .. } => {
|
||||||
|
app.move_sel(if value > 0.0 { 1 } else { -1 }, qh)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-16
@@ -1,6 +1,14 @@
|
|||||||
//! Look and layout, ported from the rofi setup this replaces (mytheme.rasi +
|
//! Look and layout.
|
||||||
//! the -theme-str rofigrid builds): gruvbox dark, a yellow selection that fills
|
//!
|
||||||
//! the element padding, and a window that hugs the grid.
|
//! The colours and spacing come from the rofi setup this replaces (mytheme.rasi
|
||||||
|
//! plus the -theme-str rofigrid built): gruvbox dark, a yellow selection filling
|
||||||
|
//! the element padding, `title · app` centred under each thumbnail.
|
||||||
|
//!
|
||||||
|
//! Sizing works from caps rather than from a thumbnail size. The config gives a
|
||||||
|
//! box the grid may fill and a column and row limit; a thumbnail is that box
|
||||||
|
//! divided by those limits. So a thumbnail is the same size whether one window
|
||||||
|
//! is open or thirty — the overlay hugs whatever is there, and rows past the
|
||||||
|
//! limit scroll.
|
||||||
|
|
||||||
/// 0xAARRGGBB, premultiplied (everything here is opaque).
|
/// 0xAARRGGBB, premultiplied (everything here is opaque).
|
||||||
pub type Argb = u32;
|
pub type Argb = u32;
|
||||||
@@ -51,10 +59,10 @@ impl Default for Theme {
|
|||||||
sel_fg: 0xff282828,
|
sel_fg: 0xff282828,
|
||||||
border: 0xffd79921,
|
border: 0xffd79921,
|
||||||
border_px: 2,
|
border_px: 2,
|
||||||
// Placeholders: the command line resolves these against the
|
// No cap of their own: Layout clamps to the display, and the
|
||||||
// display the grid will appear on.
|
// command line resolves the configured percentage over the top.
|
||||||
max_w: 1152,
|
max_w: i32::MAX,
|
||||||
max_h: 1296,
|
max_h: i32::MAX,
|
||||||
pad: 12,
|
pad: 12,
|
||||||
gap: 15,
|
gap: 15,
|
||||||
margin: 12,
|
margin: 12,
|
||||||
@@ -75,13 +83,13 @@ impl Default for Theme {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
pub cols: i32,
|
pub cols: i32,
|
||||||
/// Rows the whole grid needs, and how many of them fit on screen at once.
|
/// Rows the whole grid needs, and how many of them are on screen at once.
|
||||||
pub rows: i32,
|
pub rows: i32,
|
||||||
pub visible_rows: i32,
|
pub visible_rows: i32,
|
||||||
/// How many tiles there are, which the last row may not fill.
|
|
||||||
n: i32,
|
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
|
/// How many tiles there are, which the last row may not fill.
|
||||||
|
n: i32,
|
||||||
elem_w: i32,
|
elem_w: i32,
|
||||||
elem_h: i32,
|
elem_h: i32,
|
||||||
margin: i32,
|
margin: i32,
|
||||||
@@ -94,13 +102,14 @@ pub struct Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
/// A balanced grid: ceil(sqrt(n)) columns, capped, so the last row isn't
|
/// Lay out `n` tiles for a display of the given logical size.
|
||||||
/// ragged (6 windows -> 3x2, not 4x2 with two holes). Same rule rofigrid uses.
|
|
||||||
///
|
///
|
||||||
/// A thumbnail is the size that divides the configured box by the column and
|
/// A thumbnail is the configured box divided by the column and row caps, so
|
||||||
/// row caps, so it does not change with how many windows are open: one
|
/// it does not change with how many windows are open: one window gets a
|
||||||
/// window gets a normal thumbnail in a small overlay, thirty get the same
|
/// normal thumbnail in a small overlay, thirty get the same thumbnail and
|
||||||
/// thumbnail and scroll. The overlay then hugs whatever is actually there.
|
/// scroll. Columns follow ceil(sqrt(n)) up to the cap, so a handful of
|
||||||
|
/// windows makes a tidy grid rather than one long row — the rule rofigrid
|
||||||
|
/// used — and the overlay hugs whatever is there.
|
||||||
pub fn new(t: &Theme, n: i32, display: (i32, i32)) -> Self {
|
pub fn new(t: &Theme, n: i32, display: (i32, i32)) -> Self {
|
||||||
let n = n.max(0);
|
let n = n.max(0);
|
||||||
let (cap_cols, cap_rows) = (t.max_cols.max(1), t.max_rows.max(1));
|
let (cap_cols, cap_rows) = (t.max_cols.max(1), t.max_rows.max(1));
|
||||||
@@ -124,6 +133,7 @@ impl Layout {
|
|||||||
cols += 1;
|
cols += 1;
|
||||||
}
|
}
|
||||||
cols = cols.clamp(1, cap_cols);
|
cols = cols.clamp(1, cap_cols);
|
||||||
|
// i32::div_ceil is still unstable; only the unsigned one is not.
|
||||||
let rows = (n + cols - 1) / cols;
|
let rows = (n + cols - 1) / cols;
|
||||||
let visible_rows = cap_rows.clamp(1, rows.max(1));
|
let visible_rows = cap_rows.clamp(1, rows.max(1));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user