mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 13:30:29 +00:00
Key tables apprt action plus macOS UI (#9990)
Fixes #9963 (we'll open new issues to track GTK and other stuff) This adds the apprt actions necessary for key tables to be shown visually, and adapts the macOS UI to show them. ## Demo ``` keybind = example/ keybind = example/ctrl+a=text:hello keybind = example/ctrl+b>x=text:wow keybind = example/ctrl+c=activate_key_table:another keybind = example/escape=deactivate_key_table keybind = ctrl+a=activate_key_table:example keybind = another/ keybind = another/catch_all=deactivate_key_table ``` https://github.com/user-attachments/assets/75e94ec9-b52e-439d-b0ca-229ce533c656 **AI disclosure:** The SwiftUI view was written by AI, everything else was manual.
This commit is contained in:
@@ -691,6 +691,27 @@ typedef struct {
|
||||
ghostty_input_trigger_s trigger;
|
||||
} ghostty_action_key_sequence_s;
|
||||
|
||||
// apprt.action.KeyTable.Tag
|
||||
typedef enum {
|
||||
GHOSTTY_KEY_TABLE_ACTIVATE,
|
||||
GHOSTTY_KEY_TABLE_DEACTIVATE,
|
||||
GHOSTTY_KEY_TABLE_DEACTIVATE_ALL,
|
||||
} ghostty_action_key_table_tag_e;
|
||||
|
||||
// apprt.action.KeyTable.CValue
|
||||
typedef union {
|
||||
struct {
|
||||
const char *name;
|
||||
size_t len;
|
||||
} activate;
|
||||
} ghostty_action_key_table_u;
|
||||
|
||||
// apprt.action.KeyTable.C
|
||||
typedef struct {
|
||||
ghostty_action_key_table_tag_e tag;
|
||||
ghostty_action_key_table_u value;
|
||||
} ghostty_action_key_table_s;
|
||||
|
||||
// apprt.action.ColorKind
|
||||
typedef enum {
|
||||
GHOSTTY_ACTION_COLOR_KIND_FOREGROUND = -1,
|
||||
@@ -836,6 +857,7 @@ typedef enum {
|
||||
GHOSTTY_ACTION_FLOAT_WINDOW,
|
||||
GHOSTTY_ACTION_SECURE_INPUT,
|
||||
GHOSTTY_ACTION_KEY_SEQUENCE,
|
||||
GHOSTTY_ACTION_KEY_TABLE,
|
||||
GHOSTTY_ACTION_COLOR_CHANGE,
|
||||
GHOSTTY_ACTION_RELOAD_CONFIG,
|
||||
GHOSTTY_ACTION_CONFIG_CHANGE,
|
||||
@@ -881,6 +903,7 @@ typedef union {
|
||||
ghostty_action_float_window_e float_window;
|
||||
ghostty_action_secure_input_e secure_input;
|
||||
ghostty_action_key_sequence_s key_sequence;
|
||||
ghostty_action_key_table_s key_table;
|
||||
ghostty_action_color_change_s color_change;
|
||||
ghostty_action_reload_config_s reload_config;
|
||||
ghostty_action_config_change_s config_change;
|
||||
|
||||
@@ -141,6 +141,27 @@ extension Ghostty.Action {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum KeyTable {
|
||||
case activate(name: String)
|
||||
case deactivate
|
||||
case deactivateAll
|
||||
|
||||
init?(c: ghostty_action_key_table_s) {
|
||||
switch c.tag {
|
||||
case GHOSTTY_KEY_TABLE_ACTIVATE:
|
||||
let data = Data(bytes: c.value.activate.name, count: c.value.activate.len)
|
||||
let name = String(data: data, encoding: .utf8) ?? ""
|
||||
self = .activate(name: name)
|
||||
case GHOSTTY_KEY_TABLE_DEACTIVATE:
|
||||
self = .deactivate
|
||||
case GHOSTTY_KEY_TABLE_DEACTIVATE_ALL:
|
||||
self = .deactivateAll
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Putting the initializer in an extension preserves the automatic one.
|
||||
|
||||
@@ -578,7 +578,10 @@ extension Ghostty {
|
||||
|
||||
case GHOSTTY_ACTION_KEY_SEQUENCE:
|
||||
keySequence(app, target: target, v: action.action.key_sequence)
|
||||
|
||||
|
||||
case GHOSTTY_ACTION_KEY_TABLE:
|
||||
keyTable(app, target: target, v: action.action.key_table)
|
||||
|
||||
case GHOSTTY_ACTION_PROGRESS_REPORT:
|
||||
progressReport(app, target: target, v: action.action.progress_report)
|
||||
|
||||
@@ -1771,7 +1774,32 @@ extension Ghostty {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static func keyTable(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_key_table_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("key table does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let action = Ghostty.Action.KeyTable(c: v) else { return }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.didChangeKeyTable,
|
||||
object: surfaceView,
|
||||
userInfo: [Notification.KeyTableKey: action]
|
||||
)
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func progressReport(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
|
||||
@@ -475,6 +475,10 @@ extension Ghostty.Notification {
|
||||
static let didContinueKeySequence = Notification.Name("com.mitchellh.ghostty.didContinueKeySequence")
|
||||
static let didEndKeySequence = Notification.Name("com.mitchellh.ghostty.didEndKeySequence")
|
||||
static let KeySequenceKey = didContinueKeySequence.rawValue + ".key"
|
||||
|
||||
/// Notifications related to key tables
|
||||
static let didChangeKeyTable = Notification.Name("com.mitchellh.ghostty.didChangeKeyTable")
|
||||
static let KeyTableKey = didChangeKeyTable.rawValue + ".action"
|
||||
}
|
||||
|
||||
// Make the input enum hashable.
|
||||
|
||||
@@ -123,31 +123,11 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
// If we are in the middle of a key sequence, then we show a visual element. We only
|
||||
// support this on macOS currently although in theory we can support mobile with keyboards!
|
||||
if !surfaceView.keySequence.isEmpty {
|
||||
let padding: CGFloat = 5
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Text(verbatim: "Pending Key Sequence:")
|
||||
ForEach(0..<surfaceView.keySequence.count, id: \.description) { index in
|
||||
let key = surfaceView.keySequence[index]
|
||||
Text(verbatim: key.description)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.padding(3)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(Color(NSColor.selectedTextBackgroundColor))
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.init(top: padding, leading: padding, bottom: padding, trailing: padding))
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.background)
|
||||
}
|
||||
}
|
||||
// Show key state indicator for active key tables and/or pending key sequences
|
||||
KeyStateIndicator(
|
||||
keyTables: surfaceView.keyTables,
|
||||
keySequence: surfaceView.keySequence
|
||||
)
|
||||
#endif
|
||||
|
||||
// If we have a URL from hovering a link, we show that.
|
||||
@@ -752,6 +732,225 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
/// Floating indicator that shows active key tables and pending key sequences.
|
||||
/// Displayed as a compact draggable pill that can be positioned at the top or bottom.
|
||||
struct KeyStateIndicator: View {
|
||||
let keyTables: [String]
|
||||
let keySequence: [KeyboardShortcut]
|
||||
|
||||
@State private var isShowingPopover = false
|
||||
@State private var position: Position = .bottom
|
||||
@State private var dragOffset: CGSize = .zero
|
||||
@State private var isDragging = false
|
||||
|
||||
private let padding: CGFloat = 8
|
||||
|
||||
enum Position {
|
||||
case top, bottom
|
||||
|
||||
var alignment: Alignment {
|
||||
switch self {
|
||||
case .top: return .top
|
||||
case .bottom: return .bottom
|
||||
}
|
||||
}
|
||||
|
||||
var popoverEdge: Edge {
|
||||
switch self {
|
||||
case .top: return .top
|
||||
case .bottom: return .bottom
|
||||
}
|
||||
}
|
||||
|
||||
var transitionEdge: Edge {
|
||||
popoverEdge
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if !keyTables.isEmpty {
|
||||
content
|
||||
// Reset pointer style incase the mouse didn't move away
|
||||
.backport.pointerStyle(keyTables.isEmpty ? nil : .link)
|
||||
}
|
||||
}
|
||||
.transition(.move(edge: position.transitionEdge).combined(with: .opacity))
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: keyTables)
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: keySequence.count)
|
||||
}
|
||||
|
||||
var content: some View {
|
||||
indicatorContent
|
||||
.offset(dragOffset)
|
||||
.padding(padding)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: position.alignment)
|
||||
.highPriorityGesture(
|
||||
DragGesture(coordinateSpace: .local)
|
||||
.onChanged { value in
|
||||
isDragging = true
|
||||
dragOffset = CGSize(width: 0, height: value.translation.height)
|
||||
}
|
||||
.onEnded { value in
|
||||
isDragging = false
|
||||
let dragThreshold: CGFloat = 50
|
||||
|
||||
withAnimation(.easeOut(duration: 0.2)) {
|
||||
if position == .bottom && value.translation.height < -dragThreshold {
|
||||
position = .top
|
||||
} else if position == .top && value.translation.height > dragThreshold {
|
||||
position = .bottom
|
||||
}
|
||||
dragOffset = .zero
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var indicatorContent: some View {
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
// Key table indicator
|
||||
HStack(spacing: 5) {
|
||||
Image(systemName: "keyboard.badge.ellipsis")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
// Show table stack with arrows between them
|
||||
ForEach(Array(keyTables.enumerated()), id: \.offset) { index, table in
|
||||
if index > 0 {
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 10, weight: .semibold))
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
Text(verbatim: table)
|
||||
.font(.system(size: 13, weight: .medium, design: .rounded))
|
||||
}
|
||||
}
|
||||
|
||||
// Separator when both are active
|
||||
if !keySequence.isEmpty {
|
||||
Divider()
|
||||
.frame(height: 14)
|
||||
}
|
||||
|
||||
// Key sequence indicator
|
||||
if !keySequence.isEmpty {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
ForEach(Array(keySequence.enumerated()), id: \.offset) { index, key in
|
||||
KeyCap(key.description)
|
||||
}
|
||||
|
||||
// Animated ellipsis to indicate waiting for next key
|
||||
PendingIndicator(paused: isDragging)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background {
|
||||
Capsule()
|
||||
.fill(.regularMaterial)
|
||||
.overlay {
|
||||
Capsule()
|
||||
.strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)
|
||||
}
|
||||
.shadow(color: .black.opacity(0.2), radius: 8, y: 2)
|
||||
}
|
||||
.contentShape(Capsule())
|
||||
.backport.pointerStyle(.link)
|
||||
.popover(isPresented: $isShowingPopover, arrowEdge: position.popoverEdge) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if !keyTables.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Label("Key Table", systemImage: "keyboard.badge.ellipsis")
|
||||
.font(.headline)
|
||||
Text("A key table is a named set of keybindings, activated by some other key. Keys are interpreted using this table until it is deactivated.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if !keyTables.isEmpty && !keySequence.isEmpty {
|
||||
Divider()
|
||||
}
|
||||
|
||||
if !keySequence.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Label("Key Sequence", systemImage: "character.cursor.ibeam")
|
||||
.font(.headline)
|
||||
Text("A key sequence is a series of key presses that trigger an action. A pending key sequence is currently active.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: 400)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.onTapGesture {
|
||||
isShowingPopover.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
/// A small keycap-style view for displaying keyboard shortcuts
|
||||
struct KeyCap: View {
|
||||
let text: String
|
||||
|
||||
init(_ text: String) {
|
||||
self.text = text
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text(verbatim: text)
|
||||
.font(.system(size: 12, weight: .medium, design: .rounded))
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Color(NSColor.controlBackgroundColor))
|
||||
.shadow(color: .black.opacity(0.12), radius: 0.5, y: 0.5)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.strokeBorder(Color.primary.opacity(0.15), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Animated dots to indicate waiting for the next key
|
||||
struct PendingIndicator: View {
|
||||
@State private var animationPhase: Double = 0
|
||||
let paused: Bool
|
||||
|
||||
var body: some View {
|
||||
TimelineView(.animation(paused: paused)) { context in
|
||||
HStack(spacing: 2) {
|
||||
ForEach(0..<3, id: \.self) { index in
|
||||
Circle()
|
||||
.fill(Color.secondary)
|
||||
.frame(width: 4, height: 4)
|
||||
.opacity(dotOpacity(for: index))
|
||||
}
|
||||
}
|
||||
.onChange(of: context.date.timeIntervalSinceReferenceDate) { newValue in
|
||||
animationPhase = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dotOpacity(for index: Int) -> Double {
|
||||
let phase = animationPhase
|
||||
let offset = Double(index) / 3.0
|
||||
let wave = sin((phase + offset) * .pi * 2)
|
||||
return 0.3 + 0.7 * ((wave + 1) / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Visual overlay that shows a border around the edges when the bell rings with border feature enabled.
|
||||
struct BellBorderOverlay: View {
|
||||
let bell: Bool
|
||||
|
||||
@@ -65,6 +65,9 @@ extension Ghostty {
|
||||
// The currently active key sequence. The sequence is not active if this is empty.
|
||||
@Published var keySequence: [KeyboardShortcut] = []
|
||||
|
||||
// The currently active key tables. Empty if no tables are active.
|
||||
@Published var keyTables: [String] = []
|
||||
|
||||
// The current search state. When non-nil, the search overlay should be shown.
|
||||
@Published var searchState: SearchState? = nil {
|
||||
didSet {
|
||||
@@ -324,6 +327,11 @@ extension Ghostty {
|
||||
selector: #selector(ghosttyDidEndKeySequence),
|
||||
name: Ghostty.Notification.didEndKeySequence,
|
||||
object: self)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(ghosttyDidChangeKeyTable),
|
||||
name: Ghostty.Notification.didChangeKeyTable,
|
||||
object: self)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(ghosttyConfigDidChange(_:)),
|
||||
@@ -680,6 +688,22 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func ghosttyDidChangeKeyTable(notification: SwiftUI.Notification) {
|
||||
guard let action = notification.userInfo?[Ghostty.Notification.KeyTableKey] as? Ghostty.Action.KeyTable else { return }
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .activate(let name):
|
||||
self.keyTables.append(name)
|
||||
case .deactivate:
|
||||
_ = self.keyTables.popLast()
|
||||
case .deactivateAll:
|
||||
self.keyTables.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) {
|
||||
// Get our managed configuration object out
|
||||
guard let config = notification.userInfo?[
|
||||
|
||||
@@ -43,7 +43,10 @@ extension Ghostty {
|
||||
|
||||
// The current search state. When non-nil, the search overlay should be shown.
|
||||
@Published var searchState: SearchState? = nil
|
||||
|
||||
|
||||
// The currently active key tables. Empty if no tables are active.
|
||||
@Published var keyTables: [String] = []
|
||||
|
||||
/// True when the surface is in readonly mode.
|
||||
@Published private(set) var readonly: Bool = false
|
||||
|
||||
|
||||
@@ -5631,28 +5631,68 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
.once = tag == .activate_key_table_once,
|
||||
});
|
||||
|
||||
// Notify the UI.
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.key_table,
|
||||
.{ .activate = name },
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"failed to notify app of key table err={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
|
||||
log.debug("key table activated: {s}", .{name});
|
||||
},
|
||||
|
||||
.deactivate_key_table => switch (self.keyboard.table_stack.items.len) {
|
||||
// No key table active. This does nothing.
|
||||
0 => return false,
|
||||
.deactivate_key_table => {
|
||||
switch (self.keyboard.table_stack.items.len) {
|
||||
// No key table active. This does nothing.
|
||||
0 => return false,
|
||||
|
||||
// Final key table active, clear our state.
|
||||
1 => self.keyboard.table_stack.clearAndFree(self.alloc),
|
||||
// Final key table active, clear our state.
|
||||
1 => self.keyboard.table_stack.clearAndFree(self.alloc),
|
||||
|
||||
// Restore the prior key table. We don't free any memory in
|
||||
// this case because we assume it will be freed later when
|
||||
// we finish our key table.
|
||||
else => _ = self.keyboard.table_stack.pop(),
|
||||
// Restore the prior key table. We don't free any memory in
|
||||
// this case because we assume it will be freed later when
|
||||
// we finish our key table.
|
||||
else => _ = self.keyboard.table_stack.pop(),
|
||||
}
|
||||
|
||||
// Notify the UI.
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.key_table,
|
||||
.deactivate,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"failed to notify app of key table err={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
.deactivate_all_key_tables => switch (self.keyboard.table_stack.items.len) {
|
||||
// No key table active. This does nothing.
|
||||
0 => return false,
|
||||
.deactivate_all_key_tables => {
|
||||
switch (self.keyboard.table_stack.items.len) {
|
||||
// No key table active. This does nothing.
|
||||
0 => return false,
|
||||
|
||||
// Clear the entire table stack.
|
||||
else => self.keyboard.table_stack.clearAndFree(self.alloc),
|
||||
// Clear the entire table stack.
|
||||
else => self.keyboard.table_stack.clearAndFree(self.alloc),
|
||||
}
|
||||
|
||||
// Notify the UI.
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.key_table,
|
||||
.deactivate_all,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"failed to notify app of key table err={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
.crash => |location| switch (location) {
|
||||
|
||||
@@ -250,6 +250,9 @@ pub const Action = union(Key) {
|
||||
/// key mode because other input may be ignored.
|
||||
key_sequence: KeySequence,
|
||||
|
||||
/// A key table has been activated or deactivated.
|
||||
key_table: KeyTable,
|
||||
|
||||
/// A terminal color was changed programmatically through things
|
||||
/// such as OSC 10/11.
|
||||
color_change: ColorChange,
|
||||
@@ -371,6 +374,7 @@ pub const Action = union(Key) {
|
||||
float_window,
|
||||
secure_input,
|
||||
key_sequence,
|
||||
key_table,
|
||||
color_change,
|
||||
reload_config,
|
||||
config_change,
|
||||
@@ -711,6 +715,50 @@ pub const KeySequence = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const KeyTable = union(enum) {
|
||||
activate: []const u8,
|
||||
deactivate,
|
||||
deactivate_all,
|
||||
|
||||
// Sync with: ghostty_action_key_table_tag_e
|
||||
pub const Tag = enum(c_int) {
|
||||
activate,
|
||||
deactivate,
|
||||
deactivate_all,
|
||||
};
|
||||
|
||||
// Sync with: ghostty_action_key_table_u
|
||||
pub const CValue = extern union {
|
||||
activate: extern struct {
|
||||
name: [*]const u8,
|
||||
len: usize,
|
||||
},
|
||||
};
|
||||
|
||||
// Sync with: ghostty_action_key_table_s
|
||||
pub const C = extern struct {
|
||||
tag: Tag,
|
||||
value: CValue,
|
||||
};
|
||||
|
||||
pub fn cval(self: KeyTable) C {
|
||||
return switch (self) {
|
||||
.activate => |name| .{
|
||||
.tag = .activate,
|
||||
.value = .{ .activate = .{ .name = name.ptr, .len = name.len } },
|
||||
},
|
||||
.deactivate => .{
|
||||
.tag = .deactivate,
|
||||
.value = undefined,
|
||||
},
|
||||
.deactivate_all => .{
|
||||
.tag = .deactivate_all,
|
||||
.value = undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const ColorChange = extern struct {
|
||||
kind: ColorKind,
|
||||
r: u8,
|
||||
|
||||
@@ -744,6 +744,7 @@ pub const Application = extern struct {
|
||||
.toggle_background_opacity,
|
||||
.cell_size,
|
||||
.key_sequence,
|
||||
.key_table,
|
||||
.render_inspector,
|
||||
.renderer_health,
|
||||
.color_change,
|
||||
|
||||
Reference in New Issue
Block a user