1
1
local Widget = require (' gui.widgets.widget' )
2
2
local ResizingPanel = require (' gui.widgets.containers.resizing_panel' )
3
+ local Label = require (' gui.widgets.labels.label' )
4
+ local Panel = require (' gui.widgets.containers.panel' )
5
+ local utils = require (' utils' )
3
6
4
7
local to_pen = dfhack .pen .parse
5
8
131
134
--- @field get_pens ? fun ( index : integer , tabbar : self ): widgets.TabPens
132
135
--- @field key string
133
136
--- @field key_back string
137
+ --- @field wrap boolean
138
+ --- @field scroll_step integer
139
+ --- @field scroll_key string
140
+ --- @field scroll_key_back string
141
+ --- @field fast_scroll_modifier integer
142
+ --- @field scroll_into_view_offset integer
143
+ --- @field scroll_label_text_pen dfhack.pen
144
+ --- @field scroll_label_text_hpen dfhack.pen
134
145
135
146
--- @class widgets.TabBar.attrs.partial : widgets.TabBar.attrs
136
147
@@ -151,17 +162,41 @@ TabBar.ATTRS{
151
162
get_pens = DEFAULT_NIL ,
152
163
key = ' CUSTOM_CTRL_T' ,
153
164
key_back = ' CUSTOM_CTRL_Y' ,
165
+ wrap = true ,
166
+ scroll_step = 10 ,
167
+ scroll_key = ' CUSTOM_ALT_T' ,
168
+ scroll_key_back = ' CUSTOM_ALT_Y' ,
169
+ fast_scroll_modifier = 3 ,
170
+ scroll_into_view_offset = 5 ,
171
+ scroll_label_text_pen = DEFAULT_NIL ,
172
+ scroll_label_text_hpen = DEFAULT_NIL ,
154
173
}
155
174
175
+ local TO_THE_RIGHT = string.char (16 )
176
+ local TO_THE_LEFT = string.char (17 )
177
+
156
178
--- @param self widgets.TabBar
157
179
function TabBar :init ()
180
+ self .scrollable = false
181
+ self .scroll_offset = 0
182
+ self .first_render = true
183
+
184
+ local panel = Panel {
185
+ view_id = ' TabBar__tabs' ,
186
+ frame = {t = 0 , l = 0 , h = 2 },
187
+ frame_inset = {l = 0 },
188
+ }
189
+
158
190
for idx ,label in ipairs (self .labels ) do
159
- self :addviews {
191
+ panel :addviews {
160
192
Tab {
161
193
frame = {t = 0 , l = 0 },
162
194
id = idx ,
163
195
label = label ,
164
- on_select = self .on_select ,
196
+ on_select = function ()
197
+ self .scrollTabIntoView (self , idx )
198
+ self .on_select (idx )
199
+ end ,
165
200
get_pens = self .get_pens and function ()
166
201
return self .get_pens (idx , self )
167
202
end or function ()
@@ -174,32 +209,225 @@ function TabBar:init()
174
209
}
175
210
}
176
211
end
212
+
213
+ self :addviews {panel }
214
+
215
+ if not self .wrap then
216
+ self :addviews {
217
+ Label {
218
+ view_id = ' TabBar__scroll_left' ,
219
+ frame = {t = 0 , l = 0 , w = 1 },
220
+ text_pen = self .scroll_label_text_pen ,
221
+ text_hpen = self .scroll_label_text_hpen ,
222
+ text = TO_THE_LEFT ,
223
+ visible = false ,
224
+ on_click = function ()
225
+ self :scrollLeft ()
226
+ end ,
227
+ },
228
+ Label {
229
+ view_id = ' TabBar__scroll_right' ,
230
+ frame = {t = 0 , l = 0 , w = 1 },
231
+ text_pen = self .scroll_label_text_pen ,
232
+ text_hpen = self .scroll_label_text_hpen ,
233
+ text = TO_THE_RIGHT ,
234
+ visible = false ,
235
+ on_click = function ()
236
+ self :scrollRight ()
237
+ end ,
238
+ },
239
+ }
240
+ end
241
+ end
242
+
243
+ function TabBar :updateScrollElements ()
244
+ self :showScrollLeft ()
245
+ self :showScrollRight ()
246
+ self :updateTabPanelPosition ()
247
+ end
248
+
249
+ function TabBar :leftScrollVisible ()
250
+ return self .scroll_offset < 0
251
+ end
252
+
253
+ function TabBar :showScrollLeft ()
254
+ if self .wrap then return end
255
+ self :scrollLeftElement ().visible = self :leftScrollVisible ()
256
+ end
257
+
258
+ function TabBar :rightScrollVisible ()
259
+ return self .scroll_offset > self .offset_to_show_last_tab
260
+ end
261
+
262
+ function TabBar :showScrollRight ()
263
+ if self .wrap then return end
264
+ self :scrollRightElement ().visible = self :rightScrollVisible ()
265
+ end
266
+
267
+ function TabBar :updateTabPanelPosition ()
268
+ self :tabsElement ().frame_inset .l = self .scroll_offset
269
+ self :tabsElement ():updateLayout (self .frame_body )
270
+ end
271
+
272
+ function TabBar :tabsElement ()
273
+ return self .subviews .TabBar__tabs
274
+ end
275
+
276
+ function TabBar :scrollLeftElement ()
277
+ return self .subviews .TabBar__scroll_left
278
+ end
279
+
280
+ function TabBar :scrollRightElement ()
281
+ return self .subviews .TabBar__scroll_right
282
+ end
283
+
284
+ function TabBar :scrollTabIntoView (idx )
285
+ if self .wrap or not self .scrollable then return end
286
+
287
+ local tab = self :tabsElement ().subviews [idx ]
288
+ local tab_l = tab .frame .l
289
+ local tab_r = tab .frame .l + tab .frame .w
290
+ local tabs_l = self :tabsElement ().frame .l
291
+ local tabs_r = tabs_l + self .frame_body .width
292
+ local scroll_offset = self .scroll_offset
293
+
294
+ if tab_l < tabs_l - scroll_offset then
295
+ self .scroll_offset = tabs_l - tab_l + self .scroll_into_view_offset
296
+ elseif tab_r > tabs_r - scroll_offset then
297
+ self .scroll_offset = self .scroll_offset - (tab_r - tabs_r ) - self .scroll_into_view_offset
298
+ end
299
+
300
+ self :capScrollOffset ()
301
+ self :updateScrollElements ()
302
+ end
303
+
304
+ function TabBar :capScrollOffset ()
305
+ if self .scroll_offset > 0 then
306
+ self .scroll_offset = 0
307
+ elseif self .scroll_offset < self .offset_to_show_last_tab then
308
+ self .scroll_offset = self .offset_to_show_last_tab
309
+ end
310
+ end
311
+
312
+ function TabBar :scrollRight (alternate_step )
313
+ if not self :scrollRightElement ().visible then return end
314
+
315
+ self .scroll_offset = self .scroll_offset - (alternate_step and alternate_step or self .scroll_step )
316
+
317
+ self :capScrollOffset ()
318
+ self :updateScrollElements ()
319
+ end
320
+
321
+ function TabBar :scrollLeft (alternate_step )
322
+ if not self :scrollLeftElement ().visible then return end
323
+
324
+ self .scroll_offset = self .scroll_offset + (alternate_step and alternate_step or self .scroll_step )
325
+
326
+ self :capScrollOffset ()
327
+ self :updateScrollElements ()
328
+ end
329
+
330
+ function TabBar :isMouseOver ()
331
+ for _ , sv in ipairs (self :tabsElement ().subviews ) do
332
+ if utils .getval (sv .visible ) and sv :getMouseFramePos () then return true end
333
+ end
177
334
end
178
335
179
336
function TabBar :postComputeFrame (body )
337
+ local all_tabs_width = 0
338
+
180
339
local t , l , width = 0 , 0 , body .width
181
- for _ ,tab in ipairs (self .subviews ) do
340
+ self .scrollable = false
341
+
342
+ self .last_post_compute_width = self .post_compute_width or 0
343
+ self .post_compute_width = width
344
+
345
+ local tab_rows = 1
346
+ for _ ,tab in ipairs (self :tabsElement ().subviews ) do
347
+ tab .visible = true
182
348
if l > 0 and l + tab .frame .w > width then
183
- t = t + 2
184
- l = 0
349
+ if self .wrap then
350
+ t = t + 2
351
+ l = 0
352
+ tab_rows = tab_rows + 1
353
+ else
354
+ self .scrollable = true
355
+ end
185
356
end
186
357
tab .frame .t = t
187
358
tab .frame .l = l
188
359
l = l + tab .frame .w
360
+ all_tabs_width = all_tabs_width + tab .frame .w
189
361
end
362
+
363
+ self .offset_to_show_last_tab = - (all_tabs_width - self .post_compute_width )
364
+
365
+ if self .scrollable and not self .wrap then
366
+ self :scrollRightElement ().frame .l = width - 1
367
+ if self .last_post_compute_width ~= self .post_compute_width then
368
+ self .scroll_offset = 0
369
+ end
370
+ end
371
+
372
+ if self .first_render then
373
+ self .first_render = false
374
+ if not self .wrap then
375
+ self :scrollTabIntoView (self .get_cur_page ())
376
+ end
377
+ end
378
+
379
+ -- we have to calculate the height of this based on the number of tab rows we will have
380
+ -- so that autoarrange_subviews will work correctly
381
+ self :tabsElement ().frame .h = tab_rows * 2
382
+
383
+ self :updateScrollElements ()
384
+ end
385
+
386
+ function TabBar :fastStep ()
387
+ return self .scroll_step * self .fast_scroll_modifier
190
388
end
191
389
192
390
function TabBar :onInput (keys )
193
391
if TabBar .super .onInput (self , keys ) then return true end
392
+ if not self .wrap then
393
+ if self :isMouseOver () then
394
+ if keys .CONTEXT_SCROLL_UP then
395
+ self :scrollLeft ()
396
+ return true
397
+ end
398
+ if keys .CONTEXT_SCROLL_DOWN then
399
+ self :scrollRight ()
400
+ return true
401
+ end
402
+ if keys .CONTEXT_SCROLL_PAGEUP then
403
+ self :scrollLeft (self :fastStep ())
404
+ return true
405
+ end
406
+ if keys .CONTEXT_SCROLL_PAGEDOWN then
407
+ self :scrollRight (self :fastStep ())
408
+ return true
409
+ end
410
+ end
411
+ if self .scroll_key and keys [self .scroll_key ] then
412
+ self :scrollRight ()
413
+ return true
414
+ end
415
+ if self .scroll_key_back and keys [self .scroll_key_back ] then
416
+ self :scrollLeft ()
417
+ return true
418
+ end
419
+ end
194
420
if self .key and keys [self .key ] then
195
421
local zero_idx = self .get_cur_page () - 1
196
422
local next_zero_idx = (zero_idx + 1 ) % # self .labels
423
+ self .scrollTabIntoView (self , next_zero_idx + 1 )
197
424
self .on_select (next_zero_idx + 1 )
198
425
return true
199
426
end
200
427
if self .key_back and keys [self .key_back ] then
201
428
local zero_idx = self .get_cur_page () - 1
202
429
local prev_zero_idx = (zero_idx - 1 ) % # self .labels
430
+ self .scrollTabIntoView (self , prev_zero_idx + 1 )
203
431
self .on_select (prev_zero_idx + 1 )
204
432
return true
205
433
end
0 commit comments