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