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