Skip to content

JosephABudd/issue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Notes

  1. The build.zig.zon file expects that you have already fetched the current dvui release. If not run zig fetch https://github.com/david-vanderson/dvui/archive/refs/tags/v0.1.0.tar.gz.
  2. I'm using ubuntu 22 and wayland.

Running the app

  1. The app begins with the horizontal tab-bar displayed above tab content.
  2. Click the Align Left icon at the left side of the tab-bar to convert the horizontal tab-bar to a vertical tab-bar.
  3. On my computer, as soon as the tab-bar switches to vertical, the content area renders an unwanted vibration.
  4. Click on the Eye With Line icon to hide the vertical tab-bar and the unwanted vibration is gone.
  5. Click on the Eye icon to show the hidden vertical tab-bar and the unwanted vibration is still gone.

What I learned. 2 more ways to stop the unwanted vibration

  1. If start the app and make the window less tall before switching to the vertical tab-bar, I do not get the effect. I only get the effect if I keep the window at the same size.
  2. The file src/deps/widgets/tabbar/TabBarItemWidget.zig contains the code that seems to cause the issue. It is shown below. Lines 231 - 250 can be commented out to stop the effect.
  1const std = @import("std");
  2const dvui = @import("dvui");
  34const ContainerLabel = @import("various").ContainerLabel;
  56const Direction = dvui.enums.Direction;
  7const Event = dvui.Event;
  8const Options = dvui.Options;
  9const Rect = dvui.Rect;
 10const RectScale = dvui.RectScale;
 11const Size = dvui.Size;
 12const Widget = dvui.Widget;
 13const WidgetData = dvui.WidgetData;
 1415pub const UserSelection = enum {
 16none,
 17close_tab,
 18move_tab_right_down,
 19move_tab_left_up,
 20select_tab,
 21context,
 22 ⎥ };
 2324pub const UserAction = struct {
 25user_selection: UserSelection = .none,
 26point_of_context: dvui.Point = undefined,
 27context_widget: *dvui.ContextWidget = undefined,
 28move_from_index: usize = 0,
 29move_to_index: usize = 0,
 30 ⎥ };
 3132pub const TabBarItemWidget = @This();
 3334pub const Flow = enum {
 35horizontal,
 36vertical,
 37 ⎥ };
 3839pub const InitOptions = struct {
 40selected: ?bool = null,
 41flow: ?Flow = null,
 42id_extra: ?usize = null,
 43show_close_icon: ?bool = null,
 44show_move_icons: ?bool = null,
 45show_context_menu: ?bool = null,
 46index: ?usize = null,
 47count_tabs: ?usize = null,
 48 ⎥ };
 4950const horizontal_init_options: InitOptions = .{
 51 ⎥     .id_extra = 0,
 52 ⎥     .selected = false,
 53 ⎥     .flow = .horizontal,
 54 ⎥ };
 5556const vertical_init_options: InitOptions = .{
 57 ⎥     .id_extra = 0,
 58 ⎥     .selected = false,
 59 ⎥     .flow = .vertical,
 60 ⎥ };
 6162wd: WidgetData = undefined,
 63focused_last_frame: bool = undefined,
 64highlight: bool = false,
 65defaults: dvui.Options = undefined,
 66init_options: InitOptions = undefined,
 67activated: bool = false,
 68show_active: bool = false,
 69mouse_over: bool = false,
 7071// Defaults.
 72// Defaults for tabs in a horizontal tabbar.
 73fn horizontalDefaultOptions() dvui.Options {
 74var defaults: dvui.Options = .{
 75 ⎥         .name = "HorizontalTabBarItem",
 76 ⎥         .color_fill = .{ .name = .fill_hover },
 77 ⎥         .corner_radius = .{ .x = 2, .y = 2, .w = 0, .h = 0 },
 78 ⎥         .padding = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
 79 ⎥         .border = .{ .x = 1, .y = 1, .w = 1, .h = 0 },
 80 ⎥         .margin = .{ .x = 4, .y = 0, .w = 0, .h = 8 },
 81 ⎥         .expand = .none,
 82 ⎥         .font_style = .body,
 83// .debug = false,
 84 ⎥     };
 85const hover: dvui.Color = dvui.themeGet().color_fill_hover;
 86const hover_hsl: dvui.Color.HSLuv = dvui.Color.HSLuv.fromColor(hover);
 87const darken: dvui.Color = hover_hsl.lighten(-16).color();
 88// const darken: dvui.Color = dvui.Color.darken(hover, 0.5);
 89defaults.color_border = .{ .color = darken };
 90return defaults;
 91 ⎥ }
 9293fn horizontalDefaultSelectedOptions() dvui.Options {
 94const bg: dvui.Color = dvui.themeGet().color_fill_window;
 95var defaults = horizontalDefaultOptions();
 96defaults.color_fill = .{ .color = bg };
 97defaults.color_border = .{ .name = .accent };
 98defaults.margin = .{ .x = 4, .y = 7, .w = 0, .h = 0 };
 99100return defaults;
101 ⎥ }
102103fn verticalDefaultOptions() dvui.Options {
104var defaults: dvui.Options = .{
105 ⎥         .name = "VerticalTabBarItem",
106 ⎥         .color_fill = .{ .name = .fill_hover },
107 ⎥         .color_border = .{ .name = .fill_hover },
108 ⎥         .corner_radius = .{ .x = 2, .y = 0, .w = 0, .h = 2 },
109 ⎥         .padding = .{ .x = 0, .y = 0, .w = 1, .h = 0 },
110 ⎥         .border = .{ .x = 1, .y = 1, .w = 0, .h = 1 },
111 ⎥         .margin = .{ .x = 1, .y = 4, .w = 6, .h = 0 },
112 ⎥         .expand = .horizontal,
113 ⎥         .font_style = .body,
114 ⎥         .gravity_x = 1.0,
115 ⎥     };
116const hover: dvui.Color = dvui.themeGet().color_fill_hover;
117const hover_hsl: dvui.Color.HSLuv = dvui.Color.HSLuv.fromColor(hover);
118const darken: dvui.Color = hover_hsl.lighten(-16).color();
119// const darken: dvui.Color = dvui.Color.darken(hover, 0.5);
120defaults.color_border = .{ .color = darken };
121return defaults;
122 ⎥ }
123124pub fn verticalContextOptions() dvui.Options {
125return .{
126 ⎥         .name = "VerticalContext",
127 ⎥         .corner_radius = .{ .x = 2, .y = 0, .w = 0, .h = 2 },
128 ⎥         .padding = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
129 ⎥         .border = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
130 ⎥         .margin = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
131 ⎥         .expand = .horizontal,
132 ⎥         .gravity_x = 1.0,
133 ⎥         .background = false,
134 ⎥     };
135 ⎥ }
136137fn verticalDefaultSelectedOptions() dvui.Options {
138const bg: dvui.Color = dvui.themeGet().color_fill_window;
139var defaults = verticalDefaultOptions();
140defaults.color_fill = .{ .color = bg };
141defaults.color_border = .{ .name = .accent };
142defaults.margin = .{ .x = 7, .y = 4, .w = 0, .h = 0 };
143return defaults;
144 ⎥ }
145146pub fn verticalSelectedContextOptions() dvui.Options {
147return verticalContextOptions();
148 ⎥ }
149150/// Param label is not owned by this fn.
151pub fn verticalTabBarItemLabel(
152label: *ContainerLabel,
153init_opts: InitOptions,
154call_back: *const fn (implementor: *anyopaque, state: *anyopaque, point_of_context: dvui.Point) anyerror!void,
155call_back_implementor: *anyopaque,
156call_back_state: *anyopaque,
157 ⎥ ) !UserAction {
158var tab_init_opts: TabBarItemWidget.InitOptions = TabBarItemWidget.vertical_init_options;
159if (init_opts.id_extra) |id_extra| {
160tab_init_opts.id_extra = id_extra;
161 ⎥     }
162if (init_opts.selected) |value| {
163tab_init_opts.selected = value;
164 ⎥     }
165tab_init_opts.index = init_opts.index;
166tab_init_opts.id_extra = init_opts.index;
167tab_init_opts.count_tabs = init_opts.count_tabs;
168tab_init_opts.show_close_icon = init_opts.show_close_icon orelse true;
169tab_init_opts.show_move_icons = init_opts.show_move_icons orelse true;
170tab_init_opts.show_context_menu = init_opts.show_context_menu orelse true;
171172return tabBarItemLabel(label, tab_init_opts, .vertical, call_back, call_back_implementor, call_back_state);
173 ⎥ }
174175/// Param label is not owned by this fn.
176pub fn horizontalTabBarItemLabel(
177label: *ContainerLabel,
178init_opts: InitOptions,
179call_back: *const fn (implementor: *anyopaque, state: *anyopaque, point_of_context: dvui.Point) anyerror!void,
180call_back_implementor: *anyopaque,
181call_back_state: *anyopaque,
182 ⎥ ) !UserAction {
183var tab_init_opts: TabBarItemWidget.InitOptions = TabBarItemWidget.horizontal_init_options;
184if (init_opts.id_extra) |id_extra| {
185tab_init_opts.id_extra = id_extra;
186 ⎥     }
187if (init_opts.selected) |value| {
188tab_init_opts.selected = value;
189 ⎥     }
190tab_init_opts.index = init_opts.index;
191tab_init_opts.id_extra = init_opts.index;
192tab_init_opts.count_tabs = init_opts.count_tabs;
193tab_init_opts.show_close_icon = init_opts.show_close_icon orelse true;
194tab_init_opts.show_move_icons = init_opts.show_move_icons orelse true;
195tab_init_opts.show_context_menu = init_opts.show_context_menu orelse true;
196197return tabBarItemLabel(label, tab_init_opts, .horizontal, call_back, call_back_implementor, call_back_state);
198 ⎥ }
199200// Param label is not owned by tabBarItemLabel.
201// Display the button-label and return it's rect if clicked else null.
202// Display each icon:
203// * If icon is clicked and no cb then return button-label rect.
204// * If icon is clicked and cp then run callback and return null.
205// * CB icons only is init_opts.selected == true.
206fn tabBarItemLabel(label: *ContainerLabel, init_opts: TabBarItemWidget.InitOptions, direction: Direction, call_back: *const fn (implementor: *anyopaque, state: *anyopaque, point_of_context: dvui.Point) anyerror!void, call_back_implementor: *anyopaque, call_back_state: *anyopaque) !UserAction {
207var user_action: UserAction = UserAction{};
208const tbi = try tabBarItem(init_opts);
209210std.log.info("init_opts.show_context_menu:{}", .{init_opts.show_context_menu.?});
211const tab: *dvui.BoxWidget = try dvui.box(@src(), .horizontal, tbi.defaults);
212defer tab.deinit();
213214if (init_opts.show_context_menu.?) {
215const context_widget = try dvui.context(@src(), .{ .expand = .horizontal, .id_extra = @as(u16, @truncate(init_opts.id_extra.?)) });
216defer context_widget.deinit();
217218if (context_widget.activePoint()) |active_point| {
219// The user right mouse clicked.
220// Save this state and keep rendering this item.
221// Return the right mouse click after all is rendered.
222user_action.user_selection = .context;
223try call_back(call_back_implementor, call_back_state, active_point);
224 ⎥         }
225 ⎥     }
226227var layout: *dvui.BoxWidget = try dvui.box(@src(), .horizontal, .{});
228defer layout.deinit();
229230// If there is a badge then display it.
231if (label.badge) |badge| {
232const imgsize = try dvui.imageSize("tab badge", badge);
233try dvui.image(
234@src(),
235"tab badge",
236badge,
237 ⎥             .{
238 ⎥                 .padding = dvui.Rect{
239 ⎥                     .x = 5, // left
240 ⎥                     .y = 0, // top
241 ⎥                     .w = 0, // right
242 ⎥                     .h = 0, // bottom
243 ⎥                 },
244 ⎥                 .tab_index = 0,
245 ⎥                 .gravity_y = 0.5,
246 ⎥                 .gravity_x = 0.5,
247 ⎥                 .min_size_content = .{ .w = imgsize.w, .h = imgsize.h },
248 ⎥             },
249 ⎥         );
250 ⎥     }
251252if (try dvui.button(@src(), label.text.?, .{}, .{ .id_extra = init_opts.id_extra, .background = false })) {
253if (user_action.user_selection == .none) {
254user_action.user_selection = .select_tab;
255 ⎥         }
256return user_action;
257 ⎥     }
258259if (init_opts.show_move_icons) |show_move_icons| {
260if (show_move_icons and init_opts.index.? > 0) {
261// Move left/up icon.
262// This icon is a button.
263switch (direction) {
264.horizontal => {
265if (try dvui.buttonIcon(
266@src(),
267"entypo.chevron_small_left",
268dvui.entypo.chevron_small_left,
269 ⎥                         .{},
270 ⎥                         .{ .id_extra = init_opts.id_extra.? },
271 ⎥                     )) {
272// clicked
273if (user_action.user_selection == .none) {
274user_action.user_selection = .move_tab_left_up;
275user_action.move_from_index = init_opts.id_extra.?;
276user_action.move_to_index = init_opts.id_extra.? - 1;
277 ⎥                         }
278return user_action;
279 ⎥                     }
280 ⎥                 },
281.vertical => {
282if (try dvui.buttonIcon(
283@src(),
284"entypo.chevron_small_up",
285dvui.entypo.chevron_small_up,
286 ⎥                         .{},
287 ⎥                         .{ .id_extra = init_opts.id_extra.? },
288 ⎥                     )) {
289// clicked
290if (user_action.user_selection == .none) {
291user_action.user_selection = .move_tab_left_up;
292user_action.move_from_index = init_opts.id_extra.?;
293user_action.move_to_index = init_opts.id_extra.? - 1;
294 ⎥                         }
295return user_action;
296 ⎥                     }
297 ⎥                 },
298 ⎥             }
299 ⎥         }
300301if (show_move_icons and init_opts.index.? < init_opts.count_tabs.? - 1) {
302// Move right/down icon.
303// This icon is a button.
304switch (direction) {
305.horizontal => {
306if (try dvui.buttonIcon(
307@src(),
308"entypo.chevron_small_right",
309dvui.entypo.chevron_small_right,
310 ⎥                         .{},
311 ⎥                         .{ .id_extra = init_opts.id_extra.? },
312 ⎥                     )) {
313// clicked
314if (user_action.user_selection == .none) {
315user_action.user_selection = .move_tab_right_down;
316user_action.move_from_index = init_opts.id_extra.?;
317user_action.move_to_index = init_opts.id_extra.? + 1;
318 ⎥                         }
319return user_action;
320 ⎥                     }
321 ⎥                 },
322.vertical => {
323if (try dvui.buttonIcon(
324@src(),
325"entypo.chevron_small_down",
326dvui.entypo.chevron_small_down,
327 ⎥                         .{},
328 ⎥                         .{ .id_extra = init_opts.id_extra.? },
329 ⎥                     )) {
330// clicked
331if (user_action.user_selection == .none) {
332user_action.user_selection = .move_tab_right_down;
333user_action.move_from_index = init_opts.id_extra.?;
334user_action.move_to_index = init_opts.id_extra.? + 1;
335 ⎥                         }
336return user_action;
337 ⎥                     }
338 ⎥                 },
339 ⎥             }
340 ⎥         }
341 ⎥     }
342343// The custom icons.
344if (label.icons) |icons| {
345const icon_id_extra_base = init_opts.id_extra.? * icons.len;
346var icon_id: usize = 0;
347for (icons, 0..) |icon, i| {
348// display this icon as a button even if no callback.
349icon_id = icon_id_extra_base + i;
350const clicked: bool = try dvui.buttonIcon(
351@src(),
352icon.label.?,
353icon.tvg_bytes,
354 ⎥                 .{},
355 ⎥                 .{ .id_extra = icon_id },
356 ⎥             );
357if (clicked) {
358if (icon.call_back) |icon_call_back| {
359// This icon has a call back so call it.
360try icon_call_back(icon.implementor.?, icon.state);
361return user_action;
362 ⎥                 } else {
363// This icon has no call back so select this tab.
364if (user_action.user_selection == .none) {
365user_action.user_selection = .select_tab;
366 ⎥                     }
367return user_action;
368 ⎥                 }
369 ⎥             }
370 ⎥         }
371 ⎥     }
372373if (init_opts.show_close_icon) |show_close_icon| {
374if (show_close_icon) {
375if (try dvui.buttonIcon(
376@src(),
377"entypo.cross",
378dvui.entypo.cross,
379 ⎥                 .{},
380 ⎥                 .{},
381 ⎥             )) {
382// clicked
383if (user_action.user_selection == .none) {
384user_action.user_selection = .close_tab;
385 ⎥                 }
386return user_action;
387 ⎥             }
388 ⎥         }
389 ⎥     }
390391// No user action.
392return user_action;
393 ⎥ }
394395pub fn tabBarItem(init_opts: TabBarItemWidget.InitOptions) !TabBarItemWidget {
396return TabBarItemWidget.init(init_opts);
397 ⎥ }
398399pub fn init(init_opts: InitOptions) TabBarItemWidget {
400var self = TabBarItemWidget{};
401self.init_options = init_opts;
402self.defaults = switch (init_opts.flow.?) {
403.horizontal => blk: {
404switch (init_opts.selected.?) {
405true => break :blk horizontalDefaultSelectedOptions(),
406false => break :blk horizontalDefaultOptions(), //horizontal_defaults,
407 ⎥             }
408 ⎥         },
409.vertical => blk: {
410switch (init_opts.selected.?) {
411true => break :blk verticalDefaultSelectedOptions(),
412false => break :blk verticalDefaultOptions(),
413 ⎥             }
414 ⎥         },
415 ⎥     };
416if (init_opts.id_extra) |id_extra| {
417self.defaults.id_extra = id_extra;
418 ⎥     }
419return self;
420 ⎥ }
421422pub fn install(self: *TabBarItemWidget, opts: struct { process_events: bool = true, focus_as_outline: bool = false }) !void {
423try self.wd.register();
424425if (self.wd.visible()) {
426try dvui.tabIndexSet(self.wd.id, self.wd.options.tab_index);
427 ⎥     }
428429if (opts.process_events) {
430const evts = dvui.events();
431for (evts) |*e| {
432if (dvui.eventMatch(e, .{ .id = self.data().id, .r = self.data().borderRectScale().r })) {
433self.processEvent(e, false);
434 ⎥             }
435 ⎥         }
436 ⎥     }
437438try self.wd.borderAndBackground(.{});
439440if (self.show_active) {
441_ = dvui.parentSet(self.widget());
442return;
443 ⎥     }
444445var focused: bool = false;
446if (self.wd.id == dvui.focusedWidgetId()) {
447focused = true;
448 ⎥     } else if (self.wd.id == dvui.focusedWidgetIdInCurrentSubwindow() and self.highlight) {
449focused = true;
450 ⎥     }
451if (focused) {
452if (self.mouse_over) {
453self.show_active = true;
454// try self.wd.focusBorder();
455_ = dvui.parentSet(self.widget());
456return;
457 ⎥         } else {
458focused = false;
459self.show_active = false;
460dvui.focusWidget(null, null, null);
461 ⎥         }
462 ⎥     }
463464if ((self.wd.id == dvui.focusedWidgetIdInCurrentSubwindow()) or self.highlight) {
465const rs = self.wd.backgroundRectScale();
466try dvui.pathAddRect(rs.r, self.wd.options.corner_radiusGet().scale(rs.s));
467try dvui.pathFillConvex(self.wd.options.color(.fill_hover));
468 ⎥     } else if (self.wd.options.backgroundGet()) {
469const rs = self.wd.backgroundRectScale();
470try dvui.pathAddRect(rs.r, self.wd.options.corner_radiusGet().scale(rs.s));
471try dvui.pathFillConvex(self.wd.options.color(.fill));
472 ⎥     }
473_ = dvui.parentSet(self.widget());
474 ⎥ }
475476pub fn activeRect(self: *const TabBarItemWidget) ?dvui.Rect {
477if (self.activated) {
478const rs = self.wd.backgroundRectScale();
479return rs.r.scale(1 / dvui.windowNaturalScale());
480 ⎥     } else {
481return null;
482 ⎥     }
483 ⎥ }
484485pub fn widget(self: *TabBarItemWidget) dvui.Widget {
486return dvui.Widget.init(self, data, rectFor, screenRectScale, minSizeForChild, processEvent);
487 ⎥ }
488489pub fn data(self: *TabBarItemWidget) *dvui.WidgetData {
490return &self.wd;
491 ⎥ }
492493pub fn rectFor(self: *TabBarItemWidget, id: u32, min_size: dvui.Size, e: dvui.Options.Expand, g: dvui.Options.Gravity) dvui.Rect {
494return dvui.placeIn(self.wd.contentRect().justSize(), dvui.minSize(id, min_size), e, g);
495 ⎥ }
496497pub fn screenRectScale(self: *TabBarItemWidget, rect: dvui.Rect) dvui.RectScale {
498return self.wd.contentRectScale().rectToRectScale(rect);
499 ⎥ }
500501pub fn minSizeForChild(self: *TabBarItemWidget, s: dvui.Size) void {
502self.wd.minSizeMax(self.wd.padSize(s));
503 ⎥ }
504505pub fn processEvent(self: *TabBarItemWidget, e: *dvui.Event, bubbling: bool) void {
506_ = bubbling;
507var focused: bool = false;
508var focused_id: u32 = 0;
509if (dvui.focusedWidgetIdInCurrentSubwindow()) |_focused_id| {
510focused = self.wd.id == _focused_id;
511focused_id = _focused_id;
512 ⎥     }
513switch (e.evt) {
514.mouse => |me| {
515switch (me.action) {
516.focus => {
517e.handled = true;
518// dvui.focusSubwindow(null, null); // focuses the window we are in
519dvui.focusWidget(self.wd.id, null, e.num);
520 ⎥                 },
521.press => {
522if (me.button == dvui.enums.Button.left) {
523e.handled = true;
524 ⎥                     }
525 ⎥                 },
526.release => {
527e.handled = true;
528self.activated = true;
529dvui.refresh(null, @src(), self.data().id);
530 ⎥                 },
531.position => {
532e.handled = true;
533// We get a .position mouse event every frame.  If we
534// focus the tabBar item under the mouse even if it's not
535// moving then it breaks keyboard navigation.
536if (dvui.mouseTotalMotion().nonZero()) {
537// self.highlight = true;
538self.mouse_over = true;
539 ⎥                     }
540 ⎥                 },
541else => {},
542 ⎥             }
543 ⎥         },
544.key => |ke| {
545if (ke.code == .space and ke.action == .down) {
546e.handled = true;
547if (!self.activated) {
548self.activated = true;
549dvui.refresh(null, @src(), self.data().id);
550 ⎥                 }
551 ⎥             } else if (ke.code == .right and ke.action == .down) {
552e.handled = true;
553 ⎥             }
554 ⎥         },
555else => {},
556 ⎥     }
557558if (e.bubbleable()) {
559self.wd.parent.processEvent(e, true);
560 ⎥     }
561 ⎥ }
562563pub fn deinit(self: *TabBarItemWidget) void {
564self.wd.minSizeSetAndRefresh();
565self.wd.minSizeReportToParent();
566_ = dvui.parentSet(self.wd.parent);
567 ⎥ }
568

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages