╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ ui.zig ───────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
305 .end => { 306 if (children.count() > 0) { 307 index = children.count() - 1; 308 } 309 }, 310 .page_up => { 311 if (self.getGrid()) |grid| { 312 const half_count = (grid.size.height / 3) / 2; 313 index -|= half_count; 314 } 315 }, 316 .page_down => { 317 if (self.getGrid()) |grid| { 318 if (children.count() > 0) { 319 const half_count = (grid.size.height / 3) / 2; 320 index = @min(index + half_count, children.count() - 1); 321 } 322 } 323 }, 324 .mouse => |mouse| switch (mouse.action) { 325 .scroll => |dir| switch (dir) { 326 .up => index -|= 1, 327 .down => if (index + 1 < children.count()) { 328 index += 1; 329 }, 330 }, 331 else => {}, 332 }, 333 else => {}, 334 } 335 336 if (index != current_index) { 337 try root_focus.setFocus(children.keys()[index]); 338 self.updateScroll(index); 339 } 340 } 341 } 342 } 343 344 pub fn clearGrid(self: *SelectableList) void { 345 self.scroll.clearGrid(); 346 } 347 348 pub fn getGrid(self: SelectableList) ?Grid { 349 return self.scroll.getGrid(); 350 } 351 352 pub fn getFocus(self: *SelectableList) *Focus { 353 return self.scroll.getFocus(); 354 } 355 356 pub fn getSelectedIndex(self: SelectableList) ?usize { 357 if (self.scroll.child.box.focus.child_id) |child_id| { 358 const children = &self.scroll.child.box.children; 359 return children.getIndex(child_id); 360 } else { 361 return null; 362 } 363 } 364 365 fn updateScroll(self: *SelectableList, index: usize) void { 366 const inner_box = &self.scroll.child.box; 367 if (inner_box.children.values()[index].rect) |rect| { 368 self.scroll.scrollToRect(rect); 369 } 370 } 371 }; 372 373 // an invisible widget that fills the horizontal space granted by its parent. 374 // used inside Box(horiz) with a min_size so the box reserves space for the 375 // children that follow, pushing them to the right. 376 pub const Spacer = struct { 377 focus: Focus, 378 grid: ?Grid, 379 380 pub fn init(allocator: std.mem.Allocator) Spacer { 381 return .{ 382 .focus = Focus.init(allocator, .container), 383 .grid = null, 384 }; 385 } 386 387 pub fn deinit(self: *Spacer, allocator: std.mem.Allocator) void { 388 _ = allocator; 389 self.focus.deinit(); 390 if (self.grid) |*grid| { 391 grid.deinit(); 392 self.grid = null; 393 } 394 } 395 396 pub fn build(self: *Spacer, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 397 _ = root_focus; 398 self.clearGrid(); 399 const width = constraint.max_size.width orelse return; 400 if (width == 0) return; 401 self.grid = try Grid.init(allocator, .{ .width = width, .height = 1 }); 402 } 403 404 pub fn input(self: *Spacer, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 405 _ = self; 406 _ = allocator; 407 _ = key; 408 _ = root_focus; 409 } 410 411 pub fn clearGrid(self: *Spacer) void { 412 if (self.grid) |*grid| { 413 grid.deinit(); 414 self.grid = null; 415 } 416 } 417 418 pub fn getGrid(self: Spacer) ?Grid { 419 return self.grid; 420 } 421 422 pub fn getFocus(self: *Spacer) *Focus { 423 return &self.focus; 424 } 425 }; 426 427 // a single-child wrapper that builds the child at its natural size and 428 // positions its grid in the middle of the area granted by the parent 429 pub const Center = struct { 430 focus: Focus, 431 grid: ?Grid, 432 child: *Widget, 433 direction: Direction, 434 435 pub const Direction = enum { both, horiz, vert }; 436 437 pub fn init(allocator: std.mem.Allocator, child_widget: Widget, direction: Direction) !Center { 438 const child = try allocator.create(Widget); 439 errdefer allocator.destroy(child); 440 child.* = child_widget; 441 return .{ 442 .focus = Focus.init(allocator, .container), 443 .grid = null, 444 .child = child, 445 .direction = direction, 446 }; 447 } 448 449 pub fn deinit(self: *Center, allocator: std.mem.Allocator) void { 450 self.focus.deinit(); 451 if (self.grid) |*grid| { 452 grid.deinit(); 453 self.grid = null; 454 } 455 self.child.deinit(allocator); 456 allocator.destroy(self.child); 457 } 458 459 pub fn build(self: *Center, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 460 self.clearGrid(); 461 self.getFocus().clear(); 462 463 // build the child without forcing it to fill min_size; it sizes 464 // itself to its content within the available max. 465 try self.child.build(allocator, .{ 466 .min_size = .{ .width = null, .height = null }, 467 .max_size = constraint.max_size, 468 }, root_focus); 469 470 const child_grid = self.child.getGrid() orelse return; 471 472 // prefer max when given; otherwise grow to max(min, child) so that 473 // when only a min is set (e.g. the wasm path passes viewport rows 474 // as min_height with no max), we can still vertically center while 475 // letting taller content extend past the min. 476 const width = if (constraint.max_size.width) |w| w else if (constraint.min_size.width) |min_w| @max(min_w, child_grid.size.width) else child_grid.size.width; 477 const height = if (constraint.max_size.height) |h| h else if (constraint.min_size.height) |min_h| @max(min_h, child_grid.size.height) else child_grid.size.height; 478 if (width == 0 or height == 0) return; 479 480 const offset_x: usize = if (self.direction == .horiz or self.direction == .both) 481 (width -| child_grid.size.width) / 2 482 else 483 0; 484 const offset_y: usize = if (self.direction == .vert or self.direction == .both) 485 (height -| child_grid.size.height) / 2 486 else 487 0; 488 489 var grid = try Grid.init(allocator, .{ .width = width, .height = height }); 490 errdefer grid.deinit(); 491 try grid.drawGrid(child_grid, offset_x, offset_y); 492 try self.getFocus().addChild(self.child.getFocus(), child_grid.size, offset_x, offset_y); 493 self.getFocus().child_id = self.child.getFocus().id; 494 495 self.grid = grid; 496 } 497 498 pub fn input(self: *Center, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 499 try self.child.input(allocator, key, root_focus); 500 } 501 502 pub fn clearGrid(self: *Center) void { 503 if (self.grid) |*grid| { 504 grid.deinit(); 505 self.grid = null; 506 } 507 self.child.clearGrid(); 508 } 509 510 pub fn getGrid(self: Center) ?Grid { 511 return self.grid; 512 } 513 514 pub fn getFocus(self: *Center) *Focus { 515 return &self.focus; 516 } 517 }; 518