Skip to content

Commit 1c28bf6

Browse files
authored
feat: multiplied zoom and scroll actions (#116)
* fix: use heap allocation for view mode key bindings * fix: reset arena per frame * feat: add settings for multiplied actions * feat: include multiplied actions in PDF handler * feat: include multiplied actions in document handler * feat: support multiplied actions in view mode * docs: explain multiplied actions
1 parent 9094d11 commit 1c28bf6

6 files changed

Lines changed: 114 additions & 26 deletions

File tree

docs/config.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ Because fancy-cat provides sensible defaults, you only need to specify the optio
3232
"next": { "key": "n" },
3333
"prev": { "key": "p" },
3434
"scroll_up": { "key": "k" },
35+
"scroll_up_mult": { "key": "k", "modifiers": [ "shift" ] },
3536
"scroll_down": { "key": "j" },
37+
"scroll_down_mult": { "key": "j", "modifiers": [ "shift" ] },
3638
"scroll_left": { "key": "h" },
39+
"scroll_left_mult": { "key": "h", "modifiers": [ "shift" ] },
3740
"scroll_right": { "key": "l" },
41+
"scroll_right_mult": { "key": "l", "modifiers": [ "shift" ] },
3842
"zoom_in": { "key": "i" },
43+
"zoom_in_mult": { "key": "i", "modifiers": [ "shift" ] },
3944
"zoom_out": { "key": "o" },
45+
"zoom_out_mult": { "key": "o", "modifiers": [ "shift" ] },
4046
"width_mode": { "key": "w" },
4147
"colorize": { "key": "z" },
4248
"quit": { "key": "c", "modifiers": [ "ctrl" ] },
@@ -58,8 +64,10 @@ Because fancy-cat provides sensible defaults, you only need to specify the optio
5864
"black": "#ffffff",
5965
"size": 1.0,
6066
"zoom_step": 1.25,
67+
"zoom_mult": 2.0,
6168
"zoom_min": 1.0,
6269
"scroll_step": 100.0,
70+
"scroll_mult": 10.0,
6371
"retry_delay": 0.2,
6472
"timeout": 5.0,
6573
"detect_dpi": true,
@@ -117,11 +125,17 @@ The `KeyMap` section defines keybindings for various actions.
117125
| `next` | Go to the next page |
118126
| `prev` | Go to the previous page |
119127
| `scroll_up` | Move the viewport up |
128+
| `scroll_up_mult` | Multiplied `scroll_up` |
120129
| `scroll_down` | Move the viewport down |
130+
| `scroll_down_mult` | Multiplied `scroll_down` |
121131
| `scroll_left` | Move the viewport left |
132+
| `scroll_left_mult` | Multiplied `scroll_left` |
122133
| `scroll_right` | Move the viewport right |
134+
| `scroll_right_mult` | Multiplied `scroll_right` |
123135
| `zoom_in` | Increase the zoom level |
136+
| `zoom_in_mult` | Multiplied `zoom_in` |
124137
| `zoom_out` | Decrease the zoom level |
138+
| `zoom_out_mult` | Multiplied `zoom_out` |
125139
| `width_mode` | Toggle between full-height or full-width mode |
126140
| `colorize` | Toggle color replacement |
127141
| `quit` | Exit the program |
@@ -207,8 +221,10 @@ The `General` section includes various display and timing settings.
207221
| `black` | [Color](#color) | Replacement color for black |
208222
| `size` | Float | Initial zoom level multiplier (`1.0` fits the full height) |
209223
| `zoom_step` | Float | Zoom multiplier per keystroke |
224+
| `zoom_mult` | Float | Extra zoom multiplier for multiplied zoom actions |
210225
| `zoom_min` | Float | Minimum zoom level allowed |
211226
| `scroll_step` | Float (pixels) | Distance the viewport moves per scroll keystroke |
227+
| `scroll_mult` | Float | Scroll multiplier for multiplied scroll actions |
212228
| `detect_dpi` | Boolean | Enables pixel-density detection so that 100% zoom = actual size |
213229
| `dpi` | Float | Pixel density to use if `detect_dpi` is false, or fallback if detection fails |
214230
| `retry_delay` | Float (seconds) | Delay before retrying to load a document or render a page |

src/Context.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub const Context = struct {
182182
var buffered = self.tty.writer();
183183
try self.vx.render(buffered);
184184
try buffered.flush();
185+
186+
_ = self.arena.reset(.retain_capacity); // clear key actions from heap
185187
}
186188
}
187189

src/config/Config.zig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ pub const KeyMap = struct {
66
next: vaxis.Key = .{ .codepoint = 'n' },
77
prev: vaxis.Key = .{ .codepoint = 'p' },
88
scroll_up: vaxis.Key = .{ .codepoint = 'k' },
9+
scroll_up_mult: vaxis.Key = .{ .codepoint = 'k', .mods = .{ .shift = true } },
910
scroll_down: vaxis.Key = .{ .codepoint = 'j' },
11+
scroll_down_mult: vaxis.Key = .{ .codepoint = 'j', .mods = .{ .shift = true } },
1012
scroll_left: vaxis.Key = .{ .codepoint = 'h' },
13+
scroll_left_mult: vaxis.Key = .{ .codepoint = 'h', .mods = .{ .shift = true } },
1114
scroll_right: vaxis.Key = .{ .codepoint = 'l' },
15+
scroll_right_mult: vaxis.Key = .{ .codepoint = 'l', .mods = .{ .shift = true } },
1216
zoom_in: vaxis.Key = .{ .codepoint = 'i' },
17+
zoom_in_mult: vaxis.Key = .{ .codepoint = 'i', .mods = .{ .shift = true } },
1318
zoom_out: vaxis.Key = .{ .codepoint = 'o' },
19+
zoom_out_mult: vaxis.Key = .{ .codepoint = 'o', .mods = .{ .shift = true } },
1420
width_mode: vaxis.Key = .{ .codepoint = 'w' },
1521
colorize: vaxis.Key = .{ .codepoint = 'z' },
1622
quit: vaxis.Key = .{ .codepoint = 'c', .mods = .{ .ctrl = true } },
@@ -84,6 +90,9 @@ pub const General = struct {
8490
zoom_min: f32 = 1.0,
8591
// pixels
8692
scroll_step: f32 = 100.0,
93+
// multiplier
94+
zoom_mult: f32 = 2.0,
95+
scroll_mult: f32 = 10.0,
8796
// seconds
8897
retry_delay: f32 = 0.2,
8998
timeout: f32 = 5.0,
@@ -118,6 +127,8 @@ pub const General = struct {
118127
general.zoom_step = parseType(f32, val.object, "zoom_step", allocator, general.zoom_step);
119128
general.zoom_min = parseType(f32, val.object, "zoom_min", allocator, general.zoom_min);
120129
general.scroll_step = parseType(f32, val.object, "scroll_step", allocator, general.scroll_step);
130+
general.zoom_mult = parseType(f32, val.object, "zoom_mult", allocator, general.zoom_mult);
131+
general.scroll_mult = parseType(f32, val.object, "scroll_mult", allocator, general.scroll_mult);
121132
general.retry_delay = parseType(f32, val.object, "retry_delay", allocator, general.retry_delay);
122133
general.timeout = parseType(f32, val.object, "timeout", allocator, general.timeout);
123134
general.detect_dpi = parseType(bool, val.object, "detect_dpi", allocator, general.detect_dpi);

src/handlers/DocumentHandler.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ pub fn renderPage(
6868
return try self.pdf_handler.renderPage(page_number, window_width, window_height);
6969
}
7070

71-
pub fn zoomIn(self: *Self) void {
72-
self.pdf_handler.zoomIn();
71+
pub fn zoomIn(self: *Self, multiplier: f32) void {
72+
self.pdf_handler.zoomIn(multiplier);
7373
}
7474

75-
pub fn zoomOut(self: *Self) void {
76-
self.pdf_handler.zoomOut();
75+
pub fn zoomOut(self: *Self, multiplier: f32) void {
76+
self.pdf_handler.zoomOut(multiplier);
7777
}
7878

7979
pub fn setZoom(self: *Self, percent: f32) void {
@@ -84,8 +84,8 @@ pub fn toggleColor(self: *Self) void {
8484
self.pdf_handler.toggleColor();
8585
}
8686

87-
pub fn scroll(self: *Self, direction: types.ScrollDirection) void {
88-
self.pdf_handler.scroll(direction);
87+
pub fn scroll(self: *Self, direction: types.ScrollDirection, multiplier: f32) void {
88+
self.pdf_handler.scroll(direction, multiplier);
8989
}
9090

9191
pub fn offsetScroll(self: *Self, dx: f32, dy: f32) void {

src/handlers/PdfHandler.zig

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,14 @@ pub fn renderPage(
220220
}
221221
}
222222

223-
pub fn zoomIn(self: *Self) void {
224-
self.active_zoom *= self.config.general.zoom_step;
223+
pub fn zoomIn(self: *Self, multiplier: f32) void {
224+
const step = self.config.general.zoom_step * multiplier;
225+
self.active_zoom *= step;
225226
}
226227

227-
pub fn zoomOut(self: *Self) void {
228-
self.active_zoom /= self.config.general.zoom_step;
228+
pub fn zoomOut(self: *Self, multiplier: f32) void {
229+
const step = self.config.general.zoom_step * multiplier;
230+
self.active_zoom /= step;
229231
}
230232

231233
pub fn setZoom(self: *Self, percent: f32) void {
@@ -239,8 +241,8 @@ pub fn toggleColor(self: *Self) void {
239241
self.config.general.colorize = !self.config.general.colorize;
240242
}
241243

242-
pub fn scroll(self: *Self, direction: types.ScrollDirection) void {
243-
const step = self.config.general.scroll_step / self.active_zoom;
244+
pub fn scroll(self: *Self, direction: types.ScrollDirection, multiplier: f32) void {
245+
const step = self.config.general.scroll_step * multiplier / self.active_zoom;
244246
switch (direction) {
245247
.Up => {
246248
const translation = self.y_offset + step;

src/modes/ViewMode.zig

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ pub fn init(context: *Context) Self {
2121
pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
2222
// O(n) but n is small
2323
// Centralized key handling
24-
const key_actions = &[_]KeyAction{
24+
const allocator = self.context.arena.allocator();
25+
const key_actions = try allocator.dupe(KeyAction, &[_]KeyAction{
2526
.{
2627
.codepoint = km.next.codepoint,
2728
.mods = km.next.mods,
2829
.handler = struct {
2930
fn action(s: *Context) void {
30-
if (s.document_handler.changePage(1)) {
31-
s.resetCurrentPage();
32-
}
31+
if (s.document_handler.changePage(1)) s.resetCurrentPage();
3332
}
3433
}.action,
3534
},
@@ -38,9 +37,7 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
3837
.mods = km.prev.mods,
3938
.handler = struct {
4039
fn action(s: *Context) void {
41-
if (s.document_handler.changePage(-1)) {
42-
s.resetCurrentPage();
43-
}
40+
if (s.document_handler.changePage(-1)) s.resetCurrentPage();
4441
}
4542
}.action,
4643
},
@@ -49,7 +46,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
4946
.mods = km.zoom_in.mods,
5047
.handler = struct {
5148
fn action(s: *Context) void {
52-
s.document_handler.zoomIn();
49+
s.document_handler.zoomIn(1.0);
50+
s.reload_page = true;
51+
}
52+
}.action,
53+
},
54+
.{
55+
.codepoint = km.zoom_in_mult.codepoint,
56+
.mods = km.zoom_in_mult.mods,
57+
.handler = struct {
58+
fn action(s: *Context) void {
59+
s.document_handler.zoomIn(s.config.general.zoom_mult);
5360
s.reload_page = true;
5461
}
5562
}.action,
@@ -59,7 +66,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
5966
.mods = km.zoom_out.mods,
6067
.handler = struct {
6168
fn action(s: *Context) void {
62-
s.document_handler.zoomOut();
69+
s.document_handler.zoomOut(1.0);
70+
s.reload_page = true;
71+
}
72+
}.action,
73+
},
74+
.{
75+
.codepoint = km.zoom_out_mult.codepoint,
76+
.mods = km.zoom_out_mult.mods,
77+
.handler = struct {
78+
fn action(s: *Context) void {
79+
s.document_handler.zoomOut(s.config.general.zoom_mult);
6380
s.reload_page = true;
6481
}
6582
}.action,
@@ -91,7 +108,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
91108
.mods = km.scroll_up.mods,
92109
.handler = struct {
93110
fn action(s: *Context) void {
94-
s.document_handler.scroll(.Up);
111+
s.document_handler.scroll(.Up, 1.0);
112+
s.reload_page = true;
113+
}
114+
}.action,
115+
},
116+
.{
117+
.codepoint = km.scroll_up_mult.codepoint,
118+
.mods = km.scroll_up_mult.mods,
119+
.handler = struct {
120+
fn action(s: *Context) void {
121+
s.document_handler.scroll(.Up, s.config.general.scroll_mult);
95122
s.reload_page = true;
96123
}
97124
}.action,
@@ -101,7 +128,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
101128
.mods = km.scroll_down.mods,
102129
.handler = struct {
103130
fn action(s: *Context) void {
104-
s.document_handler.scroll(.Down);
131+
s.document_handler.scroll(.Down, 1.0);
132+
s.reload_page = true;
133+
}
134+
}.action,
135+
},
136+
.{
137+
.codepoint = km.scroll_down_mult.codepoint,
138+
.mods = km.scroll_down_mult.mods,
139+
.handler = struct {
140+
fn action(s: *Context) void {
141+
s.document_handler.scroll(.Down, s.config.general.scroll_mult);
105142
s.reload_page = true;
106143
}
107144
}.action,
@@ -111,7 +148,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
111148
.mods = km.scroll_left.mods,
112149
.handler = struct {
113150
fn action(s: *Context) void {
114-
s.document_handler.scroll(.Left);
151+
s.document_handler.scroll(.Left, 1.0);
152+
s.reload_page = true;
153+
}
154+
}.action,
155+
},
156+
.{
157+
.codepoint = km.scroll_left_mult.codepoint,
158+
.mods = km.scroll_left_mult.mods,
159+
.handler = struct {
160+
fn action(s: *Context) void {
161+
s.document_handler.scroll(.Left, s.config.general.scroll_mult);
115162
s.reload_page = true;
116163
}
117164
}.action,
@@ -121,7 +168,17 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
121168
.mods = km.scroll_right.mods,
122169
.handler = struct {
123170
fn action(s: *Context) void {
124-
s.document_handler.scroll(.Right);
171+
s.document_handler.scroll(.Right, 1.0);
172+
s.reload_page = true;
173+
}
174+
}.action,
175+
},
176+
.{
177+
.codepoint = km.scroll_right_mult.codepoint,
178+
.mods = km.scroll_right_mult.mods,
179+
.handler = struct {
180+
fn action(s: *Context) void {
181+
s.document_handler.scroll(.Right, s.config.general.scroll_mult);
125182
s.reload_page = true;
126183
}
127184
}.action,
@@ -145,7 +202,7 @@ pub fn handleKeyStroke(self: *Self, key: vaxis.Key, km: Config.KeyMap) !void {
145202
}
146203
}.action,
147204
},
148-
};
205+
});
149206

150207
for (key_actions) |action| {
151208
if (key.matches(action.codepoint, action.mods)) {

0 commit comments

Comments
 (0)