Focus and key Handling between multiple gui windows

I went down a rabbit hole of creating keyboard assignments for windows.

Then I thought it would be cool if I could also use keyboard assignments to switch between instances,
i.e

I have a synth with a window that takes keyboard shortcuts
Have multiple versions of that synth controller window open
use some assignment like cmd+[numrowkey] to go to the relavent instance

Having been all round the shop I ran into what seems like a limitation with the way supercolliders focus works with the native (Mac) OS. All the code can be working beautifully, bringing the right windows to focus, and yet the keyboard focus can get stuck where the OS thinks it is, which is only shifted by OS switching (using OS keyboard shortcut or clicking with the mouse).

In the process of my debugging, I came up with a bunch of toy examples of increasing complexity to explore different solutions and highlight the problems.

There are 2 more pattern not included here, which I believe also won’t work

  1. having a master window to receive all the keys and delegate them to slave windows. This would run into all sorts of other weird issues of focus and scope.
  2. giving all windows globalEvent handlers and just getting them to recognise if the message is for them by setting an isFocused attribute on the object. This would run into parallel issues where the path of event bubbling becomes unpredictable, and keeping track of focus between the code and the OS becomes impossible.

If I had a really pressing need to implement the original idea I think it would probably have to be done at a lower level eg HID, which really doesn’t seem worth it.

Thought I’d share my journey, and the final solution which I’m pretty pleased with.

Let me know if I missed anything.

////////////////////////////////////////////////////////   >
// BASIC - KEYBOARD DOWN/UP ACTION MAPPINGS
////////////////////////////////////////////////////////   >


// debug window with TextView to show current key messages
(
// Debug function to see key presses
// creates a window to show current keydown and following up
~debugKeyMap = { |view, char, modifiers, unicode, keycode, textView|
    var modifierText, messageText;
    modifierText = "";
    messageText = "";

    // Decode modifier flags
    if(modifiers & 1048576 > 0, { modifierText = modifierText ++ "Cmd+" });
    if(modifiers & 131072 > 0, { modifierText = modifierText ++ "Shift+" });
    if(modifiers & 262144 > 0, { modifierText = modifierText ++ "Alt+" });
    if(modifiers & 524288 > 0, { modifierText = modifierText ++ "Ctrl+" });

    messageText = messageText ++ ("\n=== KEY PRESS DEBUG ===");
    messageText = messageText ++ ("\nCharacter: '" ++ char ++ "'");
    messageText = messageText ++ ("\nUnicode: " ++ unicode);
    messageText = messageText ++ ("\nKeycode: " ++ keycode);
    messageText = messageText ++ ("\nModifiers raw: " ++ modifiers);
    messageText = messageText ++ ("\nModifiers: " ++ modifierText);
    messageText = messageText ++ ("\nIs digit: " ++ char.isDecDigit);

	// override current string
    textView.string_(messageText);
    messageText.postln;
};

~debugKeyUpMap = { |view, char, modifiers, unicode, keycode ,textView|
	var messageText = ("\n\nKEY UP: '" ++ char ++ "' modifiers: " ++ modifiers);
	var currentString = textView.string;
	// add the String rather than overriding
	textView.string_(currentString++messageText);
    messageText.postln;
    ("").postln;
};

// Create debug window
w = Window("Keyboard Debug", Rect(100, 100, 500, 400));
StaticText(w, Rect(20, 20, 460, 120))
    .string_("Keyboard Debug Window\n\nPress any keys and check the post window\nfor detailed key information")
    .font_(Font("Monaco", 14));
t = TextView(w, Rect(20, 150, 460, 200));

// assign keyDownAction to the function we want to use
w.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
    ~debugKeyMap.(view, char, modifiers, unicode, keycode, t);
};
w.view.keyUpAction = { |view, char, modifiers, unicode, keycode|
    ~debugKeyUpMap.(view, char, modifiers, unicode, keycode, t);
};
w.front;
w.view.focus;
)















// WIDGET FOCUS WITHIN A WINDOW

////////////////////////////////////////////////////////   >
// Approach 1: Simple example with manual focus highlighting
////////////////////////////////////////////////////////   >


(
var window, textField1, textField2, button;
var focusedColor, unfocusedColor, focusedBorderColor, unfocusedBorderColor;

// Define colors
focusedColor = Color.yellow(0.3);
unfocusedColor = Color.white;
focusedBorderColor = Color.blue;
unfocusedBorderColor = Color.gray(0.7);

window = Window("Focus Demo - Simple", Rect(100, 100, 400, 200));

// TextField 1 with manual focus handling
textField1 = TextField(window, Rect(20, 20, 150, 30))
    .string_("Field 1")
    .background_(unfocusedColor);

textField1.focusGainedAction = {
    textField1.background_(focusedColor);
    ("TextField 1 gained focus").postln;
};

textField1.focusLostAction = {
    textField1.background_(unfocusedColor);
    ("TextField 1 lost focus").postln;
};

// TextField 2 with manual focus handling
textField2 = TextField(window, Rect(20, 60, 150, 30))
    .string_("Field 2")
    .background_(unfocusedColor);

textField2.focusGainedAction = {
    textField2.background_(focusedColor);
    ("TextField 2 gained focus").postln;
};

textField2.focusLostAction = {
    textField2.background_(unfocusedColor);
    ("TextField 2 lost focus").postln;
};

// Button (buttons don't have background color, so we'll use a different approach)
button = Button(window, Rect(20, 100, 100, 30))
    .states_([["Click Me"]])
    .focusColor_(focusedBorderColor);

button.focusGainedAction = {
    ("Button gained focus").postln;
};

button.focusLostAction = {
    ("Button lost focus").postln;
};

// Window focus tracking
window.onClose = { ("Window closed").postln; };
window.toFrontAction = { ("Window came to front").postln; };

window.front;
)






////////////////////////////////////////////////////////   >
// Approach 2: Comprehensive system with automatic focus
// highlighting with feedback using a dictionary of GUI elemetnts (widgets)
////////////////////////////////////////////////////////   >



(
var window, widgets, focusManager;
var createFocusManager, addFocusTracking;

// Focus manager that handles multiple widget types
createFocusManager = {
    var manager, focusedColor, unfocusedColor, currentFocused;

    focusedColor = Color.yellow(0.2);
    unfocusedColor = Color.white;
    currentFocused = nil;

    manager = Dictionary.new;

    // Method to add focus tracking to any widget
    manager[\addWidget] = { |widget, name, statusWidget|
        var originalBg;

        // Store original background if it exists
        originalBg = if(widget.respondsTo(\background), { widget.background }, { Color.white });

        // Set up focus gained action
        if(widget.respondsTo(\focusGainedAction), {
            widget.focusGainedAction = {
                // Clear previous focused widget
                if(currentFocused.notNil, {
                    if(currentFocused[\widget].respondsTo(\background), {
                        currentFocused[\widget].background_(currentFocused[\originalBg]);
                    });
                });

                // Highlight current widget
                if(widget.respondsTo(\background), {
                    widget.background_(focusedColor);
                });

                // Update tracking
                currentFocused = (widget: widget, originalBg: originalBg, name: name);
                ("Focus gained: " ++ name).postln;

                // Update status display in real-time
                if(statusWidget.notNil, {
                    statusWidget.string_("Currently focused: " ++ name);
                });
            };
        });

        // Set up focus lost action
        if(widget.respondsTo(\focusLostAction), {
            widget.focusLostAction = {
                if(widget.respondsTo(\background), {
                    widget.background_(originalBg);
                });
                ("Focus lost: " ++ name).postln;

                // Update status to show nothing focused
                if(statusWidget.notNil, {
                    statusWidget.string_("Currently focused: None");
                });
            };
        });

        // Special handling for buttons (use focusColor instead)
        if(widget.class == Button, {
            widget.focusColor_(Color.blue);
        });

        ("Added focus tracking to: " ++ name).postln;
    };

    // Method to get currently focused widget info
    manager[\getCurrentFocus] = {
        if(currentFocused.notNil, {
            currentFocused[\name];
        }, {
            "None";
        });
    };

    manager;
};

// Create the focus manager
focusManager = createFocusManager.value;

// Create window and widgets
window = Window("Focus Demo - Comprehensive", Rect(200, 200, 500, 350));

// Create various widget types
widgets = Dictionary.new;

widgets[\textField1] = TextField(window, Rect(20, 20, 150, 30))
    .string_("Text Field 1");

widgets[\textField2] = TextField(window, Rect(200, 20, 150, 30))
    .string_("Text Field 2");

widgets[\numberBox1] = NumberBox(window, Rect(20, 60, 80, 30))
    .value_(42);

widgets[\numberBox2] = NumberBox(window, Rect(120, 60, 80, 30))
    .value_(3.14);

widgets[\slider] = Slider(window, Rect(20, 100, 200, 30))
    .value_(0.5);

widgets[\button1] = Button(window, Rect(20, 140, 100, 30))
    .states_([["Button 1"]]);

widgets[\button2] = Button(window, Rect(140, 140, 100, 30))
    .states_([["Button 2"]]);

widgets[\textView] = TextView(window, Rect(20, 180, 300, 80))
    .string_("This is a TextView\nTry clicking here too");

// Status display
widgets[\statusText] = StaticText(window, Rect(20, 270, 400, 60))
    .string_("Click on different widgets to see focus tracking\nCheck the post window for focus events")
    .font_(Font("Arial", 12));

// Add focus tracking to all widgets
widgets.keysValuesDo({ |key, widget|
    if(key != \statusText, {  // Don't track the status text itself
        focusManager[\addWidget].(widget, key.asString, widgets[\statusText]);
    });
});

// Add a button to check current focus
widgets[\checkFocusBtn] = Button(window, Rect(370, 20, 100, 30))
    .states_([["Check Focus"]])
    .action_({
        var current = focusManager[\getCurrentFocus].value;
        ("Currently focused: " ++ current).postln;
        widgets[\statusText].string_("Currently focused: " ++ current);
    });

focusManager[\addWidget].(widgets[\checkFocusBtn], "checkFocusBtn", widgets[\statusText]);

// Window-level focus tracking
window.toFrontAction = {
    ("Window gained focus").postln;
};

window.endFrontAction = {
    ("Window lost focus").postln;
};

window.front;
)














////////////////////////////////////////////////////////   >
// MULTI-WINDOW FOCUS TRACKING,
// with WIDGET FOCUS WITHIN A WINDOW
////////////////////////////////////////////////////////   >


(
var windows, widgets, focusManager, globalFocusState;
var createWindow, createFocusManager, createKeyDebugFunctions;

// Global state to track focus across all windows
globalFocusState = Dictionary.new;
globalFocusState[\activeWindow] = nil;
globalFocusState[\activeWidget] = nil;

// Enhanced focus manager that handles multiple windows
createFocusManager = {
    var manager, focusedColor, unfocusedColor;

    focusedColor = Color.yellow(0.2);
    unfocusedColor = Color.white;

    manager = Dictionary.new;

    // Add widget focus tracking with window awareness
    manager[\addWidget] = { |widget, name, statusWidget, windowName|
        var originalBg;

        originalBg = if(widget.respondsTo(\background), { widget.background }, { Color.white });

        if(widget.respondsTo(\focusGainedAction), {
            widget.focusGainedAction = {
                // Update global focus state
                globalFocusState[\activeWidget] = (name: name, window: windowName);

                // Clear previous focused widget if it exists
                if(manager[\currentFocused].notNil, {
                    if(manager[\currentFocused][\widget].respondsTo(\background), {
                        manager[\currentFocused][\widget].background_(manager[\currentFocused][\originalBg]);
                    });
                });

                // Highlight current widget
                if(widget.respondsTo(\background), {
                    widget.background_(focusedColor);
                });

                // Update tracking
                manager[\currentFocused] = (widget: widget, originalBg: originalBg, name: name, window: windowName);

                ("Widget focus gained: " ++ name ++ " in window: " ++ windowName).postln;

                // Update all status displays
                if(statusWidget.notNil, {
                    statusWidget.string_("Active: " ++ windowName ++ " -> " ++ name);
                });
            };
        });

        if(widget.respondsTo(\focusLostAction), {
            widget.focusLostAction = {
                if(widget.respondsTo(\background), {
                    widget.background_(originalBg);
                });

                ("Widget focus lost: " ++ name ++ " in window: " ++ windowName).postln;
            };
        });

        // Special handling for buttons
        if(widget.class == Button, {
            widget.focusColor_(Color.blue);
        });
    };

    manager[\currentFocused] = nil;
    manager;
};

// Keyboard debug functions
createKeyDebugFunctions = {
    var debugKeyDown, debugKeyUp;

    debugKeyDown = { |view, char, modifiers, unicode, keycode, textView, windowName|
        var modifierText, messageText;
        modifierText = "";
        messageText = "";

        // Decode modifier flags
        if(modifiers & 1048576 > 0, { modifierText = modifierText ++ "Cmd+" });
        if(modifiers & 131072 > 0, { modifierText = modifierText ++ "Shift+" });
        if(modifiers & 262144 > 0, { modifierText = modifierText ++ "Alt+" });
        if(modifiers & 524288 > 0, { modifierText = modifierText ++ "Ctrl+" });

        messageText = messageText ++ ("=== " ++ windowName ++ " KEY DEBUG ===");
        messageText = messageText ++ ("\nChar: '" ++ char ++ "' | Code: " ++ keycode);
        messageText = messageText ++ ("\nModifiers: " ++ modifierText);
        messageText = messageText ++ ("\nTime: " ++ Date.localtime.format("%H:%M:%S"));

        textView.string_(messageText);
        ("Key received in " ++ windowName ++ ": '" ++ char ++ "'").postln;
    };

    debugKeyUp = { |view, char, modifiers, unicode, keycode, textView, windowName|
        var currentString, keyUpMsg;
        currentString = textView.string;
        keyUpMsg = "\n\nKEY UP: '" ++ char ++ "'";
        textView.string_(currentString ++ keyUpMsg);
        ("Key up in " ++ windowName ++ ": '" ++ char ++ "'").postln;
    };

    (keyDown: debugKeyDown, keyUp: debugKeyUp);
};

// Function to create a window with focus tracking
createWindow = { |windowName, rect|
    var window, statusText, textField1, textField2, button, keyboardDebugView;
    var windowDict, keyDebugFuncs;

    keyDebugFuncs = createKeyDebugFunctions.value;
    window = Window(windowName, rect);

    // Window-level focus tracking
    window.toFrontAction = {
        globalFocusState[\activeWindow] = windowName;
        ("Window gained focus: " ++ windowName).postln;

        // Update all status displays across all windows
        windows.do({ |winDict|
            if(winDict[\statusText].notNil, {
                winDict[\statusText].string_("Active Window: " ++ windowName);
            });
        });
    };

    window.endFrontAction = {
        ("Window lost focus: " ++ windowName).postln;

        // When window loses focus, we might still have a widget "focused"
        // but it won't receive keyboard input
        if(globalFocusState[\activeWidget].notNil, {
            ("Widget '" ++ globalFocusState[\activeWidget][\name] ++
             "' may still appear focused but won't receive keyboard input").postln;
        });
    };

    // Create widgets for this window
    statusText = StaticText(window, Rect(10, 10, 350, 25))
        .string_("Window: " ++ windowName ++ " | Click widgets to see focus tracking")
        .font_(Font("Arial", 10))
        .background_(Color.gray(0.9));

    textField1 = TextField(window, Rect(10, 40, 120, 25))
        .string_("Text Field 1");

    textField2 = TextField(window, Rect(140, 40, 120, 25))
        .string_("Text Field 2");

    button = Button(window, Rect(270, 40, 80, 25))
        .states_([["Button"]]);

    // Keyboard debug view - shows which window actually receives keys
    StaticText(window, Rect(10, 70, 350, 15))
        .string_("Keyboard Input Debug (press any key):")
        .font_(Font("Arial", 9));

    keyboardDebugView = TextView(window, Rect(10, 90, 350, 80))
        .string_("No keyboard input yet...")
        .font_(Font("Monaco", 9))
        .background_(Color.gray(0.95));

    // Set up keyboard debugging for this window
    window.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
        keyDebugFuncs[\keyDown].(view, char, modifiers, unicode, keycode, keyboardDebugView, windowName);
    };

    window.view.keyUpAction = { |view, char, modifiers, unicode, keycode|
        keyDebugFuncs[\keyUp].(view, char, modifiers, unicode, keycode, keyboardDebugView, windowName);
    };

    // Button to test keyboard focus
    Button(window, Rect(10, 175, 140, 25))
        .states_([["Test Keyboard Input"]])
        .action_({
            ("Testing keyboard focus in window: " ++ windowName).postln;
            if(globalFocusState[\activeWindow] == windowName, {
                ("This window HAS OS focus - keyboard works").postln;
            }, {
                ("This window may NOT have OS focus - keyboard may not work").postln;
            });
        });

    // Button to show global focus state
    Button(window, Rect(160, 175, 150, 25))
        .states_([["Show Global Focus"]])
        .action_({
            ("=== GLOBAL FOCUS STATE ===").postln;
            ("Active Window: " ++ globalFocusState[\activeWindow]).postln;
            if(globalFocusState[\activeWidget].notNil, {
                ("Active Widget: " ++ globalFocusState[\activeWidget][\name] ++
                 " in " ++ globalFocusState[\activeWidget][\window]).postln;
            }, {
                ("Active Widget: None").postln;
            });
        });

    // Button to clear keyboard debug display
    Button(window, Rect(320, 175, 40, 25))
        .states_([["Clear"]])
        .action_({
            keyboardDebugView.string_("Cleared...");
        });

    // Create window dictionary
    windowDict = Dictionary.new;
    windowDict[\window] = window;
    windowDict[\statusText] = statusText;
    windowDict[\widgets] = Dictionary.new;
    windowDict[\widgets][\textField1] = textField1;
    windowDict[\widgets][\textField2] = textField2;
    windowDict[\widgets][\button] = button;

    windowDict;
};

// Create focus manager
focusManager = createFocusManager.value;

// Create multiple windows
windows = Array.new;

windows = windows.add(createWindow.value("Window A", Rect(100, 100, 380, 210)));
windows = windows.add(createWindow.value("Window B", Rect(500, 100, 380, 210)));
windows = windows.add(createWindow.value("Window C", Rect(300, 350, 380, 210)));

// Add focus tracking to all widgets in all windows
windows.do({ |winDict|
    var windowName = winDict[\window].name;

    winDict[\widgets].keysValuesDo({ |key, widget|
        focusManager[\addWidget].(widget, key.asString, winDict[\statusText], windowName);
    });
});

// Show all windows
windows.do({ |winDict| winDict[\window].front; });

// Set initial focus to first window
windows[0][\window].front;

("=== Multi-Window Focus Demo Started ===").postln;
("Try clicking between windows and widgets to see focus tracking").postln;
("Check the post window for detailed focus events").postln;
("Use 'Show Global Focus' buttons to see current state").postln;
)













////////////////////////////////////////////////////////   >
// LIMITATIONS OF NATIVE FOCUS SWITCHING WITH GLOBAL KEYMAPPING
//  NEED Actual OS focus, gets out of sync when set through Supercollider Qt
////////////////////////////////////////////////////////   >


// Simple Window Switcher - Clear hierarchy approach
(
var win1, win2, win3;
// Simple Window Switcher - Clear hierarchy approach
var windowManager, createTestWindow;

// Simple window manager with clear focus handling
windowManager = (
	windows: Dictionary.new,
	activeKey: nil,

	// Keycodes for number row (1-9)
	numberKeycodes: (
		18: 1, 19: 2, 20: 3, 21: 4, 23: 5,
		22: 6, 26: 7, 28: 8, 25: 9
	),

	addWindow: { |self, key, window|
		self.windows[key] = window;

		// Set up window focus tracking
		window.toFrontAction = {
			("Window " ++ key ++ " came to front").postln;
			self.activeKey = key;
			self.updateFocusIndicators(key);
		};

		window.endFrontAction = {
			("Window " ++ key ++ " lost front").postln;
		};

		// First window becomes active
		if(self.activeKey.isNil, {
			self.activeKey = key;
			self.updateFocusIndicators(key);
		});
	},

	switchToWindow: { |self, key|
		var window, currentWindow;
		window = self.windows[key];
		if(window.notNil, {
			("Switching to window: " ++ key).postln;

			// First, explicitly remove focus from current window
			if(self.activeKey.notNil, {
				currentWindow = self.windows[self.activeKey];
				if(currentWindow.notNil, {
					currentWindow.view.focus(false);
					("Removed focus from: " ++ self.activeKey).postln;
				});
			});

			// Then bring new window to front and focus it
			window.front;  // OS front
			window.view.focus(true);  // Internal focus - may not work reliably
			("Set focus to: " ++ key).postln;

			self.activeKey = key;

			// Update visual indicators
			self.updateFocusIndicators(key);

			// Show limitation message
			("LIMITATION: You may need to click in the window to establish keyboard focus").postln;
		}, {
			("Window " ++ key ++ " not found").postln;
		});
	},

	updateFocusIndicators: { |self, activeKey|
		self.windows.keysValuesDo({ |key, window|
			var statusWidget = window.view.children[0]; // First child is status text
			if(key == activeKey, {
				statusWidget.background_(Color.red(0.3));
				statusWidget.string_("Window: " ++ key ++ " [SHOULD HAVE FOCUS - click if needed]");
			}, {
				statusWidget.background_(Color.gray(0.7));
				statusWidget.string_("Window: " ++ key ++ " [inactive]");
			});
		});
	},

	handleGlobalKey: { |self, keycode, modifiers|
		var windowNumber = self.numberKeycodes[keycode];
		var isCmdPressed = (modifiers & 1048576) > 0;

		// Debug what we're seeing
		("Global handler - keycode: " ++ keycode ++ ", windowNumber: " ++ windowNumber ++ ", isCmdPressed: " ++ isCmdPressed).postln;

		// ONLY consume Cmd+number combinations
		if(windowNumber.notNil && isCmdPressed, {
			var windowKeys = self.windows.keys.asArray.sort;
			if(windowNumber <= windowKeys.size, {
				var targetKey = windowKeys[windowNumber - 1];
				("Consuming global key - switching to: " ++ targetKey).postln;
				self.switchToWindow(targetKey);
				^true;  // Event consumed - this was a window switch command
			});
		});
		("Not consuming - passing to local handler").postln;
		false;  // Event NOT consumed - let it go to local handler
	}
);

// Function to create a test window
createTestWindow = { |name, rect, color|
	var window, textField, keyDebugView, statusText;

	window = Window(name, rect);

	// Status display
	statusText = StaticText(window, Rect(10, 10, 300, 20))
	.string_("Window: " ++ name)
	.background_(color)
	.font_(Font("Arial", 12));

	// Text field for testing focus
	/*textField = TextField(window, Rect(10, 40, 200, 25))
	.string_("Type here to test focus");
	*/
	// Keyboard debug view
	keyDebugView = TextView(window, Rect(10, 75, 300, 100))
	.string_("Keyboard debug - press keys...")
	.font_(Font("Monaco", 10))
	.background_(Color.gray(0.95))
	.editable_(false);

	// Window-level keyboard handling
	window.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
		var consumed = false;

		// First, try global window switching
		consumed = windowManager.handleGlobalKey(keycode, modifiers);

		("Local handler in " ++ name ++ " - consumed: " ++ consumed).postln;

		// If not consumed by global handler, handle locally
		if(consumed.not, {

			var modStr = "";
			var debugText;
			("Running local handler for: " ++ char).postln;
			if((modifiers & 1048576) > 0, { modStr = modStr ++ "Cmd+" });
			if((modifiers & 131072) > 0, { modStr = modStr ++ "Shift+" });
			if((modifiers & 262144) > 0, { modStr = modStr ++ "Alt+" });
			if((modifiers & 524288) > 0, { modStr = modStr ++ "Ctrl+" });

			debugText = "Key in " ++ name ++ ":\n";
			debugText = debugText ++ "Char: '" ++ char ++ "'\n";
			debugText = debugText ++ "Keycode: " ++ keycode ++ "\n";
			debugText = debugText ++ "Modifiers: " ++ modStr ++ "\n";
			debugText = debugText ++ "Time: " ++ Date.localtime.format("%H:%M:%S");

			keyDebugView.string_(debugText);
			("Local key in " ++ name ++ ": '" ++ char ++ "'").postln;
		}, {
			("Global handler consumed the event").postln;
		});
	};

	// Add click handler to establish focus when clicking anywhere in window
	window.view.mouseDownAction = { |view, x, y, modifiers, buttonNumber, clickCount|
		("Clicked in " ++ name ++ " - establishing focus").postln;
		windowManager.activeKey = name.asSymbol;
		windowManager.updateFocusIndicators(name.asSymbol);
	};

	// Clear button
	Button(window, Rect(220, 40, 60, 25))
	.states_([["Clear"]])
	.action_({ keyDebugView.string_("Cleared..."); });

	// Button to manually switch windows
	Button(window, Rect(10, 185, 100, 25))
	.states_([["Switch Test"]])
	.action_({
		var keys = windowManager.windows.keys.asArray;
		var otherKeys = keys.reject({ |k| k == name.asSymbol });
		if(otherKeys.size > 0, {
			windowManager.switchToWindow(otherKeys[0]);
		});
	});

	// Instructions
	StaticText(window, Rect(10, 220, 300, 60))
	.string_("Cmd+1/2/3: Switch windows\nType in text field to test focus\n\nLIMITATION: Click in window after switching\nto establish keyboard focus")
	.font_(Font("Arial", 10))
	.background_(Color.yellow(0.1));

	window;
};

// Create test windows
win1 = createTestWindow.value("Window 1", Rect(100, 100, 330, 300), Color.yellow(0.3));
win2 = createTestWindow.value("Window 2", Rect(450, 100, 330, 300), Color.blue(0.3));
win3 = createTestWindow.value("Window 3", Rect(275, 420, 330, 300), Color.green(0.3));

// Add to window manager
windowManager.addWindow(\win1, win1);
windowManager.addWindow(\win2, win2);
windowManager.addWindow(\win3, win3);

// Show all windows
win1.front;
win2.front;
win3.front;

// Set focus to first window
windowManager.switchToWindow(\win1);

("=== Complete Window Switcher Example ===").postln;
("WHAT WORKS:").postln;
("- Cmd+1/2/3 switches windows visually").postln;
("- Local keyboard handling works when focus is established").postln;
("- Visual indicators show intended focus state").postln;
("- Click detection helps establish focus").postln;
("").postln;
("LIMITATION:").postln;
("- Programmatic focus transfer is unreliable").postln;
("- You must CLICK in a window after switching to establish keyboard focus").postln;
("- This is a SuperCollider limitation, not a bug in our code").postln;
("").postln;
("TRY THIS:").postln;
("1. Press Cmd+1 to switch to Window 1").postln;
("2. Click anywhere in Window 1 to establish focus").postln;
("3. Press some keys - they should appear in Window 1's debug area").postln;
("4. Repeat with Cmd+2/3 for other windows").postln;
)


part 2 of the code

////////////////////////////////////////////////////////   >
// DOCUMENT MODE
// NOTE: This is For Dicument whcih is Based on the Text Document currently running on the langauge side at Root,
// NOT Guis, so this will ONLY execute global commands when Supercollider IDE is in focus
////////////////////////////////////////////////////////   >


(
var win1, win2, win3;
var windowManager, createTestWindow;
// Document.globalKeyDownAction Solution - True Global Commands

// Window manager that uses Document-level global handling
windowManager = (
    windows: Dictionary.new,
    activeKey: nil,

    // Keycodes for number row (1-9)
    numberKeycodes: (
        18: 1, 19: 2, 20: 3, 21: 4, 23: 5,
        22: 6, 26: 7, 28: 8, 25: 9
    ),

    addWindow: { |self, key, window|
        self.windows[key] = window;

        // First window becomes active
        if(self.activeKey.isNil, {
            self.activeKey = key;
            self.updateActiveIndicators;
        });
    },

    setActiveWindow: { |self, key|
		var targetWindow;
        if(self.windows[key].notNil, {
            ("Setting active window to: " ++ key).postln;
            self.activeKey = key;
            self.updateActiveIndicators;

            // More aggressive window activation
            targetWindow = self.windows[key];
            targetWindow.front;
            targetWindow.view.focus(true);
            ("Brought " ++ key ++ " to front and focused").postln;
        }, {
            ("Window " ++ key ++ " not found").postln;
        });
    },

    updateActiveIndicators: { |self|
        self.windows.keysValuesDo({ |key, window|
            var statusWidget = window.view.children[0];
            if(key == self.activeKey, {
                statusWidget.background_(Color.green(0.4));
                statusWidget.string_("Window: " ++ key ++ " [ACTIVE]");
            }, {
                statusWidget.background_(Color.gray(0.7));
                statusWidget.string_("Window: " ++ key ++ " [inactive]");
            });
        });
    },

    // This gets called by Document.globalKeyDownAction
    handleGlobalKey: { |self, char, modifiers, keycode|
        var windowNumber = self.numberKeycodes[keycode];
        var isCmdPressed = (modifiers & 1048576) > 0;

        ("Global handler: char='" ++ char ++ "' keycode=" ++ keycode ++ " cmd=" ++ isCmdPressed).postln;

        // Handle Cmd+number for window switching
        if(windowNumber.notNil && isCmdPressed, {
            var windowKeys = self.windows.keys.asArray.sort;
            if(windowNumber <= windowKeys.size, {
                var targetKey = windowKeys[windowNumber - 1];
                ("Global window switch to: " ++ targetKey).postln;
                self.setActiveWindow(targetKey);
                true; // Consume event
            }, {
                false; // Don't consume
            });
        }, {
            false; // Don't consume - let it go to focused window
        });
    },

    // Call this to set up global handling
    setupGlobalHandler: { |self|
        Document.globalKeyDownAction = { |view, char, modifiers, unicode, keycode|
            var consumed = self.handleGlobalKey(char, modifiers, keycode);

            if(consumed, {
                ("Global: consumed event").postln;
            }, {
                ("Global: passing to focused window").postln;
            });

            // Return true to consume, false to pass through
            consumed;
        };

        ("Global key handler installed").postln;
    },

    // Clean up global handler
    removeGlobalHandler: { |self|
        Document.globalKeyDownAction = nil;
        ("Global key handler removed").postln;
    }
);

// Function to create a test window (simpler now)
createTestWindow = { |name, rect, color|
    var window, textField, keyDebugView, statusText, localCounter, counterDisplay;

    localCounter = 0;

    window = Window(name, rect);

    // Status display
    statusText = StaticText(window, Rect(10, 10, 300, 20))
        .string_("Window: " ++ name)
        .background_(color)
        .font_(Font("Arial", 12));

    // Text field for testing focus
    /*textField = TextField(window, Rect(10, 40, 200, 25))
        .string_("Type here to test focus");
*/
    // Keyboard debug view
    keyDebugView = TextView(window, Rect(10, 75, 300, 80))
        .string_("Local keyboard debug...")
        .font_(Font("Monaco", 10))
	.editable_(false)
        .background_(Color.gray(0.95));

    // Local counter display
    counterDisplay = StaticText(window, Rect(10, 165, 200, 20))
        .string_("Space bar presses: " ++ localCounter)
        .font_(Font("Arial", 11));

    // NORMAL window-level keyboard handling (only gets non-consumed events)
    window.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
        var debugText;
		var modStr = "";
        if((modifiers & 1048576) > 0, { modStr = modStr ++ "Cmd+" });
        if((modifiers & 131072) > 0, { modStr = modStr ++ "Shift+" });
        if((modifiers & 262144) > 0, { modStr = modStr ++ "Alt+" });
        if((modifiers & 524288) > 0, { modStr = modStr ++ "Ctrl+" });

        debugText = "LOCAL in " ++ name ++ ":\n";
        debugText = debugText ++ "Char: '" ++ char ++ "' | Code: " ++ keycode ++ "\n";
        debugText = debugText ++ "Modifiers: " ++ modStr ++ "\n";
        debugText = debugText ++ "Time: " ++ Date.localtime.format("%H:%M:%S");

        keyDebugView.string_(debugText);

        // Count space bar presses
        if(char == $ , {
            localCounter = localCounter + 1;
            counterDisplay.string_("Space bar presses: " ++ localCounter);
            ("Space pressed in " ++ name ++ " (count: " ++ localCounter ++ ")").postln;
        });

        ("Local key in " ++ name ++ ": '" ++ char ++ "'").postln;
    };

    // Clear button (works normally)
    Button(window, Rect(220, 40, 60, 25))
        .states_([["Clear"]])
        .action_({
            keyDebugView.string_("Cleared...");
            localCounter = 0;
            counterDisplay.string_("Space bar presses: " ++ localCounter);
        });

    // Test button (works normally)
    Button(window, Rect(10, 195, 150, 25))
        .states_([["Test Button"]])
        .action_({
            localCounter = localCounter + 5;
            counterDisplay.string_("Space bar presses: " ++ localCounter);
            ("Button pressed in " ++ name ++ " - added 5 to counter").postln;
        });

    // Instructions
    StaticText(window, Rect(10, 230, 300, 80))
        .string_("DOCUMENT GLOBAL APPROACH:\n• Global handler intercepts ALL key events\n• Cmd+1/2/3 consumed globally (work everywhere)\n• Other keys passed to focused window normally\n• Buttons work normally (no artificial restrictions)")
        .font_(Font("Arial", 9))
        .background_(Color.yellow(0.1));

    window;
};

// Create test windows
win1 = createTestWindow.value("Window 1", Rect(100, 100, 330, 330), Color.yellow(0.3));
win2 = createTestWindow.value("Window 2", Rect(450, 100, 330, 330), Color.blue(0.3));
win3 = createTestWindow.value("Window 3", Rect(275, 450, 330, 330), Color.green(0.3));

// Add to window manager
windowManager.addWindow(\win1, win1);
windowManager.addWindow(\win2, win2);
windowManager.addWindow(\win3, win3);

// Set up the global handler
windowManager.setupGlobalHandler;

// Show all windows
win1.front;
win2.front;
win3.front;

// Set first window as active
windowManager.setActiveWindow(\win1);

("=== DOCUMENT GLOBAL KEY HANDLER SOLUTION ===").postln;
("").postln;
("HOW IT WORKS:").postln;
("• Document.globalKeyDownAction intercepts ALL key events BEFORE they reach windows").postln;
("• Global commands (Cmd+1/2/3) are consumed at document level - work from ANYWHERE").postln;
("• Other keys are passed through to whichever window has focus").postln;
("• No artificial restrictions - buttons and text fields work normally").postln;
("").postln;
("TRY THIS:").postln;
("1. Press Cmd+1/2/3 from ANY window - global switching works").postln;
("2. Type in text fields - works normally in focused window").postln;
("3. Press Space in different windows - each counts independently").postln;
("4. Click buttons - they work normally").postln;
("").postln;
("To clean up: windowManager.removeGlobalHandler").postln;
)






////////////////////////////////////////////////////////   >
// FINAL SOLUTION
// Stick with OS level focus (don't try and swim against the tide)
// Use a focus manager  as a high level interface to set focus state behaviour for GUI elements, and keep a stop of what is in focus in case I need it
// Can optionally Add extra callbacks to individual elements onFocusGained / onFocusLost with this pattern

// TODO: would be a good idea to turn this a class / quark

////////////////////////////////////////////////////////   >



// Clean Focus Management Pattern - Production Ready
(
// Global focus state (optional - only if you need cross-window awareness)
~globalFocus = ~globalFocus ?? { Dictionary.new };

// Reusable Focus Manager
~focusManager = (
    // Visual settings
    focusedColor: Color.yellow(0.3),
    unfocusedColor: Color.white,
    focusedButtonColor: Color.blue,

    // Internal state
    currentFocused: nil,

    // Add focus highlighting to any widget
    addWidget: { |self, widget, name, parentObject|
        var originalBg;

        // Store original background
        originalBg = if(widget.respondsTo(\background), { widget.background }, { Color.white });

        // Set up focus gained behavior
        if(widget.respondsTo(\focusGainedAction), {
            widget.focusGainedAction = {
                // Clear previous highlight
                if(self.currentFocused.notNil, {
                    if(self.currentFocused[\widget].respondsTo(\background), {
                        self.currentFocused[\widget].background_(self.currentFocused[\originalBg]);
                    });
                });

                // Highlight current widget
                if(widget.respondsTo(\background), {
                    widget.background_(self.focusedColor);
                });

                // Update tracking
                self.currentFocused = (widget: widget, originalBg: originalBg, name: name);

                // Optional: update global state
                if(~globalFocus.notNil, {
                    ~globalFocus[\activeWidget] = (name: name, parent: parentObject);
                });

                // Optional: notify parent object
                if(parentObject.respondsTo(\onWidgetFocused), {
                    parentObject.onWidgetFocused(name, widget);
                });
            };
        });

        // Set up focus lost behavior
        if(widget.respondsTo(\focusLostAction), {
            widget.focusLostAction = {
                if(widget.respondsTo(\background), {
                    widget.background_(originalBg);
                });

                // Optional: notify parent object
                if(parentObject.respondsTo(\onWidgetUnfocused), {
                    parentObject.onWidgetUnfocused(name, widget);
                });
            };
        });

        // Special handling for buttons (they don't have background)
        if(widget.class == Button, {
            widget.focusColor_(self.focusedButtonColor);
        });
    },
	
	addCompositeWidget: { |self, composite, name, parent|
		composite.children.do({ |child, i|
			if(child.respondsTo(\focusGainedAction), {
				self.addWidget(child, name ++ "_" ++ i, parent);
			});
		});
	},

    // Get currently focused widget info
    getCurrentFocus: { |self|
        if(self.currentFocused.notNil, {
            self.currentFocused[\name];
        }, {
            nil;
        });
    },

    // Clear all focus highlighting
    clearFocus: { |self|
        if(self.currentFocused.notNil, {
            if(self.currentFocused[\widget].respondsTo(\background), {
                self.currentFocused[\widget].background_(self.currentFocused[\originalBg]);
            });
            self.currentFocused = nil;
        });
    }
);

// Window Focus Manager - for tracking window-level focus
~windowFocusManager = (
    activeWindow: nil,
    windows: Dictionary.new,

    // Add window focus tracking
    addWindow: { |self, window, name, parentObject|
        self.windows[name] = (window: window, parent: parentObject);

        // Set up window focus tracking
        window.toFrontAction = {
            self.activeWindow = name;

            // Optional: update global state
            if(~globalFocus.notNil, {
                ~globalFocus[\activeWindow] = name;
            });

            // Optional: notify parent object
            if(parentObject.respondsTo(\onWindowFocused), {
                parentObject.onWindowFocused(name);
            });
        };

        window.endFrontAction = {
            // Optional: notify parent object
            if(parentObject.respondsTo(\onWindowUnfocused), {
                parentObject.onWindowUnfocused(name);
            });
        };
    },

    getActiveWindow: { |self|
        self.activeWindow;
    }
);

// Example: Simple GUI object using the focus pattern
~simpleGUI = (
    window: nil,
    focusManager: nil,
    name: "SimpleGUI",

    // Optional: focus event handlers
    onWidgetFocused: { |self, widgetName, widget|
        // Custom behavior when a widget gains focus
        // ("Widget focused: " ++ widgetName).postln;
    },

    onWidgetUnfocused: { |self, widgetName, widget|
        // Custom behavior when a widget loses focus
        // ("Widget unfocused: " ++ widgetName).postln;
    },

    onWindowFocused: { |self, windowName|
        // Custom behavior when window gains focus
        // ("Window focused: " ++ windowName).postln;
    },

    onWindowUnfocused: { |self, windowName|
        // Custom behavior when window loses focus
        // ("Window unfocused: " ++ windowName).postln;
    },

    makeGui: { |self|
        var layout = VLayout();
		var textField1, textField2, numberBox, button, slider, layoutViewSlider;

        // Create focus manager for this GUI
        self.focusManager = ~focusManager.copy;

        self.window = Window(self.name, Rect(100, 100, 300, 200));

        // Create some widgets
        textField1 = TextField()
            .string_("Text Field 1");

        textField2 = TextField()
            .string_("Text Field 2");

        numberBox = NumberBox()
            .value_(42);

        button = Button()
            .states_([["Click Me"]]);

      slider = Slider()
            .value_(0.5);
		// Test if LayoutValueSlider responds to focus tracking
		layoutViewSlider = LayoutValueSlider();
layoutViewSlider.focusGainedAction = { "LayoutValueSlider gained focus".postln; };
layoutViewSlider.focusLostAction = { "LayoutValueSlider lost focus".postln; };

// Try adding it to your focus manager
		// focusManager.addWidget(slider, "testSlider", self);

        // Add focus tracking to all widgets
        self.focusManager.addWidget(textField1, "textField1", self);
        self.focusManager.addWidget(textField2, "textField2", self);
        self.focusManager.addWidget(numberBox, "numberBox", self);
        self.focusManager.addWidget(button, "button", self);
        self.focusManager.addWidget(slider, "slider", self);
		 self.focusManager.addCompositeWidget(layoutViewSlider, "layout view slider", self);

        // Add to layout
        layout.add(StaticText().string_("Focus Pattern Demo").align_(\center));
        layout.add(textField1);
        layout.add(textField2);
        layout.add(numberBox);
        layout.add(button);
        layout.add(slider);
		layout.add(layoutViewSlider);

        // Button to show current focus
        layout.add(
            Button()
                .states_([["Show Current Focus"]])
                .action_({
                    var current = self.focusManager.getCurrentFocus;
                    if(current.notNil, {
                        ("Currently focused: " ++ current).postln;
                    }, {
                        ("No widget focused").postln;
                    });
                })
        );

        self.window.layout = layout;

        // Add window focus tracking
        ~windowFocusManager.addWindow(self.window, self.name, self);

        self.window.front;
    },

    close: { |self|
        if(self.window.notNil, {
            self.window.close;
        });
    }
);

("=== Clean Focus Management Pattern Ready ===").postln;
("").postln;
("USAGE:").postln;
("// Create a GUI with focus highlighting").postln;
("~myGUI = ~simpleGUI.copy;").postln;
("~myGUI.name = \"MyApp\";").postln;
("~myGUI.makeGui;").postln;
("").postln;
("FEATURES:").postln;
("• Automatic focus highlighting (yellow background)").postln;
("• Button focus colors (blue)").postln;
("• Optional global focus state tracking").postln;
("• Optional focus event callbacks").postln;
("• Reusable pattern for any GUI").postln;
("").postln;
("TO APPLY TO YOUR HARMONIC ORGAN:").postln;
("• Create focusManager = ~focusManager.copy in makeGui").postln;
("• Call focusManager.addWidget(widget, name, self) for each widget").postln;
("• Optional: implement onWidgetFocused/onWindowFocused methods").postln;
)