diff --git a/.gitignore b/.gitignore index 7b07a4a..c4b76d2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules event/ build/ reference/ +src/*/*.md QWEN.md diff --git a/.justfile b/.justfile index 08c6f34..d691315 100644 --- a/.justfile +++ b/.justfile @@ -1,4 +1,9 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] +sync-path := if os_family() == "windows" { + "C:\\Users\\sikongjueluo\\AppData\\Roaming\\CraftOS-PC\\computer\\0\\user\\" +} else { + "/home/sikongjueluo/.local/share/craftos-pc/computer/0/user/" +} build: build-autocraft build-accesscontrol build-test build-example sync @@ -16,4 +21,4 @@ build-example: pnpm tstl -p ./tsconfig.tuiExample.json sync: - cp -r "./build/*" "C:\\Users\\sikongjueluo\\AppData\\Roaming\\CraftOS-PC\\computer\\0\\user\\" + rsync --delete -r "./build/" "{{sync-path}}" diff --git a/devenv.nix b/devenv.nix index 298fc57..54a9730 100644 --- a/devenv.nix +++ b/devenv.nix @@ -3,6 +3,8 @@ { packages = with pkgs; [ pnpm + craftos-pc + qwen-code ]; # https://devenv.sh/languages/ diff --git a/package.json b/package.json index 21a847a..b959606 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "@jackmacwindows/lua-types": "^2.13.2", "@jackmacwindows/typescript-to-lua": "^1.28.1", "@sikongjueluo/advanced-peripherals-types": "file:types/advanced-peripherals", - "@sikongjueluo/toml2lua-types": "file:types/toml2lua", - "@sikongjueluo/dkjson-types": "file:types/dkjson", + "@sikongjueluo/dkjson-types": "file:thirdparty/dkjson", + "@sikongjueluo/toml2lua-types": "file:thirdparty/toml2lua", "@typescript-to-lua/language-extensions": "^1.19.0", "eslint": "^9.36.0", "typescript": "^5.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27e23bc..1280fa4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,11 +27,11 @@ importers: specifier: file:types/advanced-peripherals version: file:types/advanced-peripherals '@sikongjueluo/dkjson-types': - specifier: file:types/dkjson - version: file:types/dkjson + specifier: file:thirdparty/dkjson + version: file:thirdparty/dkjson '@sikongjueluo/toml2lua-types': - specifier: file:types/toml2lua - version: file:types/toml2lua + specifier: file:thirdparty/toml2lua + version: file:thirdparty/toml2lua '@typescript-to-lua/language-extensions': specifier: ^1.19.0 version: 1.19.0 @@ -132,11 +132,11 @@ packages: '@sikongjueluo/advanced-peripherals-types@file:types/advanced-peripherals': resolution: {directory: types/advanced-peripherals, type: directory} - '@sikongjueluo/dkjson-types@file:types/dkjson': - resolution: {directory: types/dkjson, type: directory} + '@sikongjueluo/dkjson-types@file:thirdparty/dkjson': + resolution: {directory: thirdparty/dkjson, type: directory} - '@sikongjueluo/toml2lua-types@file:types/toml2lua': - resolution: {directory: types/toml2lua, type: directory} + '@sikongjueluo/toml2lua-types@file:thirdparty/toml2lua': + resolution: {directory: thirdparty/toml2lua, type: directory} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -689,9 +689,9 @@ snapshots: '@sikongjueluo/advanced-peripherals-types@file:types/advanced-peripherals': {} - '@sikongjueluo/dkjson-types@file:types/dkjson': {} + '@sikongjueluo/dkjson-types@file:thirdparty/dkjson': {} - '@sikongjueluo/toml2lua-types@file:types/toml2lua': {} + '@sikongjueluo/toml2lua-types@file:thirdparty/toml2lua': {} '@types/estree@1.0.8': {} diff --git a/src/lib/ccTUI.ts b/src/lib/ccTUI.ts index 9dd110b..aca40b7 100644 --- a/src/lib/ccTUI.ts +++ b/src/lib/ccTUI.ts @@ -5,7 +5,7 @@ */ // Import required types from the ComputerCraft environment -import { CharEvent, KeyEvent, pullEventAs } from "./event"; +import { CharEvent, KeyEvent, pullEventAs, TimerEvent } from "./event"; import { CCLog, DAY } from "./ccLog"; /** @@ -38,24 +38,34 @@ class Signal { } abstract class UIObject { - private objectName: string; + readonly objectName: string; private parent?: UIObject; private children: Record = {}; - private log?: CCLog; - constructor(name: string) { + public log?: CCLog; + + constructor(name: string, parent?: UIObject, log?: CCLog) { this.objectName = name; + this.parent = parent; + this.log = log; } public setParent(parent: UIObject) { this.parent = parent; + this.log ??= parent.log; } public addChild(child: UIObject) { this.children[child.objectName] = child; } - public removeChild(child: UIObject) {} + public removeChild(child: UIObject) { + Object.entries(this.children).forEach(([key, value]) => { + if (value === child) { + delete this.children[key]; + } + }); + } } /** @@ -75,7 +85,8 @@ abstract class UIComponent extends UIObject { public onKeyPress = new Signal(); public onMouseClick = new Signal<{ x: number; y: number }>(); - constructor(x: number, y: number, width = 0, height = 0) { + constructor(objectName: string, x: number, y: number, width = 0, height = 0) { + super(objectName); this.x = x; this.y = y; this.width = width; @@ -85,11 +96,20 @@ abstract class UIComponent extends UIObject { // Render the component to the terminal abstract render(): void; - // Handle input events + // Handle events // Key - abstract handleKeyInput(event: KeyEvent): void; + handleKeyInput(_event: KeyEvent): void { + // Do nothing + } + // Char - abstract handleCharInput(event: CharEvent): void; + handleCharInput(_event: CharEvent): void { + // Do nothing + } + + handleTimerTrigger(_event: TimerEvent): void { + // Do nothing + } // Get/set focus for the component focus(): void { @@ -151,13 +171,14 @@ class TextLabel extends UIComponent { private bgColor: number; constructor( + objectName: string, x: number, y: number, text: string, textColor: number = colors.white, bgColor: number = colors.black, ) { - super(x, y, text.length, 1); + super(objectName, x, y, text.length, 1); this.text = text; this.textColor = textColor; this.bgColor = bgColor; @@ -180,14 +201,6 @@ class TextLabel extends UIComponent { term.setCursorPos(originalX, originalY); } - handleKeyInput(_event: KeyEvent): void { - // Do nothing - } - - handleCharInput(_event: CharEvent): void { - // Do nothing - } - setText(newText: string): void { this.text = newText; this.width = newText.length; // Update width based on new text @@ -213,6 +226,7 @@ class InputField extends UIComponent { public onTextChanged = new Signal(); constructor( + objectName: string, x: number, y: number, width: number, @@ -221,7 +235,7 @@ class InputField extends UIComponent { maxLength = 50, password = false, ) { - super(x, y, width, 1); + super(objectName, x, y, width, 1); this.value = value; this.placeholder = placeholder; this.maxLength = maxLength; @@ -286,7 +300,7 @@ class InputField extends UIComponent { this.onKeyPress.emit(event); const key = event.key; - log.debug(`[${InputField.name}]: Get key ${keys.getName(key)}`); + this.log?.debug(`[${InputField.name}]: Get key ${keys.getName(key)}`); // Handle backspace if (key === keys.backspace) { @@ -334,7 +348,7 @@ class InputField extends UIComponent { if (!this.focused) return; const character = event.character; - log.debug(`[${InputField.name}]: Get character ${character}`); + this.log?.debug(`[${InputField.name}]: Get character ${character}`); this.value += character; this.cursorPos++; @@ -385,13 +399,14 @@ class OptionSelector extends UIComponent { public onSelectionChanged = new Signal<{ index: number; value: string }>(); constructor( + objectName: string, x: number, y: number, options: string[], prompt = "Select:", initialIndex = 0, ) { - super(x, y, 0, 1); // Width will be calculated dynamically + super(objectName, x, y, 0, 1); // Width will be calculated dynamically this.options = options; this.currentIndex = initialIndex; this.prompt = prompt; @@ -454,10 +469,6 @@ class OptionSelector extends UIComponent { } } - handleCharInput(_event: CharEvent): void { - //Do nothing - } - private previousOption(): void { this.currentIndex = (this.currentIndex - 1 + this.options.length) % this.options.length; @@ -509,6 +520,430 @@ class OptionSelector extends UIComponent { } } +/** + * Tab component that allows switching between different pages + * Similar to QT's TabWidget, currently implementing horizontal tabs only + */ +class TabWidget extends UIComponent { + // Tab data structure - simple array of tab names + private tabs: string[]; + private currentIndex: number; + // Tracks visible range of tabs to handle overflow scenarios + private firstVisibleIndex: number; + private lastVisibleIndex: number; + + // Signal emitted when the current tab changes + public onTabChanged = new Signal<{ index: number; name: string }>(); + + /** + * Creates a new TabWidget component + * @param objectName Unique name for the component + * @param x X position on the terminal + * @param y Y position on the terminal + * @param width Width of the tab widget + * @param tabNames Initial list of tab names + * @param initialIndex Index of the initially selected tab (default: 0) + */ + constructor( + objectName: string, + x: number, + y: number, + width: number, + tabNames: string[], + initialIndex = 0, + ) { + super(objectName, x, y, width, 1); + + // Initialize tabs as simple string array + this.tabs = [...tabNames]; + this.currentIndex = Math.max( + 0, + Math.min(initialIndex, tabNames.length - 1), + ); + this.firstVisibleIndex = 0; + this.lastVisibleIndex = -1; + + // Calculate which tabs can be displayed based on available width + this.updateVisibleRange(); + } + + /** + * Updates the range of visible tabs based on available width + * This method ensures the current tab is always visible and calculates + * which other tabs can fit in the available space + */ + private updateVisibleRange(): void { + // If no tabs exist, nothing to update + if (this.tabs.length === 0) { + this.firstVisibleIndex = 0; + this.lastVisibleIndex = -1; + return; + } + + // Calculate visible tabs range based on current position + this.calculateVisibleTabs(); + } + + /** + * Calculates visible tabs based on current position and available width + * Follows the new core rendering logic + */ + private calculateVisibleTabs(): void { + if (this.tabs.length === 0) { + this.firstVisibleIndex = 0; + this.lastVisibleIndex = -1; + return; + } + + // Start with all tabs and build the complete string + let fullString = "| "; + for (let i = 0; i < this.tabs.length; i++) { + if (i > 0) { + fullString += " | "; + } + fullString += this.tabs[i]; + } + fullString += " |"; + + // If the full string fits, show all tabs + if (fullString.length <= this.width) { + this.firstVisibleIndex = 0; + this.lastVisibleIndex = this.tabs.length - 1; + return; + } + + // Find the range that can fit around the current tab + this.firstVisibleIndex = this.currentIndex; + this.lastVisibleIndex = this.currentIndex; + + // Try to expand left and right alternately + while ( + this.firstVisibleIndex > 0 || + this.lastVisibleIndex < this.tabs.length - 1 + ) { + let expanded = false; + + // Try expanding left first + if (this.firstVisibleIndex > 0) { + const newTestString = + "| " + + this.tabs[this.firstVisibleIndex - 1] + + " | " + + this.tabs + .slice(this.firstVisibleIndex, this.lastVisibleIndex + 1) + .join(" | ") + + " |"; + + if (newTestString.length <= this.width) { + this.firstVisibleIndex--; + expanded = true; + } + } + + // Try expanding right + if (this.lastVisibleIndex < this.tabs.length - 1) { + const newTestString = + "| " + + this.tabs + .slice(this.firstVisibleIndex, this.lastVisibleIndex + 1) + .join(" | ") + + " | " + + this.tabs[this.lastVisibleIndex + 1] + + " |"; + + if (newTestString.length <= this.width) { + this.lastVisibleIndex++; + expanded = true; + } + } + + // If no expansion was possible, break + if (!expanded) break; + } + } + + /** + * Renders the tab widget to the terminal + * Follows the new core rendering logic: + * 1. Build complete string with all visible tabs + * 2. Calculate what can be displayed + * 3. Replace indicators based on hidden tabs + * 4. Determine highlight range and render + */ + render(): void { + if (!this.visible) return; + + const [originalX, originalY] = term.getCursorPos(); + + // Move cursor to the position of the tab widget + term.setCursorPos(this.x, this.y); + + if (this.tabs.length === 0) { + // Fill with spaces if no tabs + term.setTextColor(colors.white); + term.setBackgroundColor(colors.black); + term.write(" ".repeat(this.width)); + term.setCursorPos(originalX, originalY); + return; + } + + // Step 1: Build complete string for visible tabs with "| " at start and " |" at end + let displayString = "| "; + for (let i = this.firstVisibleIndex; i <= this.lastVisibleIndex; i++) { + if (i > this.firstVisibleIndex) { + displayString += " | "; + } + displayString += this.tabs[i]; + } + displayString += " |"; + + // Step 2: Check if the string fits, if not, truncate with "..." + if (displayString.length > this.width) { + // Need to truncate - find where to cut and add "..." + const maxLength = this.width - 3; // Reserve space for "..." + if (maxLength > 0) { + // Find the last complete tab that can fit + let cutPosition = maxLength; + // Try to cut at a tab boundary if possible + let lastPipePos = -1; + for (let i = cutPosition; i >= 0; i--) { + if (displayString.substring(i, i + 3) === " | ") { + lastPipePos = i; + break; + } + } + if (lastPipePos > 2) { + // Make sure we don't cut before the first tab + cutPosition = lastPipePos; + } + displayString = displayString.substring(0, cutPosition) + "..."; + } else { + displayString = "..."; + } + } + + // Step 3: Replace boundary indicators based on hidden tabs + if (this.firstVisibleIndex > 0) { + // Left side has hidden tabs - replace "| " with "< " + displayString = "< " + displayString.substring(2); + } + + if (this.lastVisibleIndex < this.tabs.length - 1) { + // Right side has hidden tabs - replace " |" with " >" + if (displayString.endsWith(" |")) { + displayString = + displayString.substring(0, displayString.length - 2) + " >"; + } else if (displayString.endsWith("...")) { + // If we have "...", just ensure we show ">" + displayString = + displayString.substring(0, displayString.length - 3) + " >"; + } + } + + // Pad to maintain consistent width + while (displayString.length < this.width) { + displayString += " "; + } + + // Ensure we don't exceed the width + if (displayString.length > this.width) { + displayString = displayString.substring(0, this.width); + } + + // Step 4: Find current tab position for highlighting + let currentTabStart = -1; + let currentTabEnd = -1; + + if ( + this.currentIndex >= this.firstVisibleIndex && + this.currentIndex <= this.lastVisibleIndex + ) { + // Calculate position of current tab in display string + let searchPos = 2; // Start after "| " or "< " + + // Find current tab position by iterating through visible tabs + for (let i = this.firstVisibleIndex; i <= this.lastVisibleIndex; i++) { + if (i > this.firstVisibleIndex) { + searchPos += 3; // " | " separator + } + + if (i === this.currentIndex) { + currentTabStart = searchPos; + + // Find the end of the current tab + const tabName = this.tabs[i]; + const remainingString = displayString.substring(searchPos); + + // Check if the tab is fully displayed or truncated + if (remainingString.startsWith(tabName)) { + // Tab is fully displayed + currentTabEnd = searchPos + tabName.length; + } else { + // Tab might be truncated, find where it ends + const nextSeparatorPos = remainingString.indexOf(" |"); + const nextIndicatorPos = remainingString.indexOf(" >"); + const ellipsisPos = remainingString.indexOf("..."); + + let endPos = remainingString.length; + if (nextSeparatorPos >= 0) + endPos = Math.min(endPos, nextSeparatorPos); + if (nextIndicatorPos >= 0) + endPos = Math.min(endPos, nextIndicatorPos); + if (ellipsisPos >= 0) endPos = Math.min(endPos, ellipsisPos); + + currentTabEnd = searchPos + endPos; + } + break; + } + + searchPos += this.tabs[i].length; + } + } + + // Step 5: Render with highlighting + term.setTextColor(colors.white); + term.setBackgroundColor(colors.black); + + if (currentTabStart >= 0 && currentTabEnd > currentTabStart) { + // Render text before current tab + if (currentTabStart > 0) { + term.write(displayString.substring(0, currentTabStart)); + } + + // Render current tab with highlighting + term.setTextColor(colors.yellow); + term.setBackgroundColor(colors.gray); + term.write(displayString.substring(currentTabStart, currentTabEnd)); + + // Reset colors and render remaining text + term.setTextColor(colors.white); + term.setBackgroundColor(colors.black); + term.write(displayString.substring(currentTabEnd)); + } else { + // No highlighting needed, render entire string + term.write(displayString); + } + + // Restore original cursor position + term.setCursorPos(originalX, originalY); + } + + /** + * Handles key input events for the tab widget + * Supports left/right arrow keys to switch between tabs + * @param event The key event to handle + */ + handleKeyInput(event: KeyEvent): void { + if (!this.focused) return; + + this.onKeyPress.emit(event); + + const key = event.key; + + // Handle left arrow to move to previous visible tab + if (key === keys.left && this.canMoveToPreviousTab()) { + this.moveToPreviousTab(); + } + // Handle right arrow to move to next visible tab + else if (key === keys.right && this.canMoveToNextTab()) { + this.moveToNextTab(); + } + } + + /** + * Checks if there is a previous tab available to move to + * @returns True if there's a previous tab, false otherwise + */ + private canMoveToPreviousTab(): boolean { + return this.currentIndex > 0; + } + + /** + * Moves to the previous tab + */ + private moveToPreviousTab(): void { + if (this.currentIndex > 0) { + this.currentIndex--; + this.updateVisibleRange(); + this.onTabChanged.emit({ + index: this.currentIndex, + name: this.tabs[this.currentIndex], + }); + } + } + + /** + * Checks if there is a next tab available to move to + * @returns True if there's a next tab, false otherwise + */ + private canMoveToNextTab(): boolean { + return this.currentIndex < this.tabs.length - 1; + } + + /** + * Moves to the next tab + */ + private moveToNextTab(): void { + if (this.currentIndex < this.tabs.length - 1) { + this.currentIndex++; + this.updateVisibleRange(); + this.onTabChanged.emit({ + index: this.currentIndex, + name: this.tabs[this.currentIndex], + }); + } + } + + /** + * Gets the index of the currently selected tab + * @returns The index of the current tab + */ + getCurrentTabIndex(): number { + return this.currentIndex; + } + + /** + * Gets the name of the currently selected tab + * @returns The name of the current tab + */ + getCurrentTabName(): string { + if (this.currentIndex >= 0 && this.currentIndex < this.tabs.length) { + return this.tabs[this.currentIndex]; + } + return ""; + } + + /** + * Sets the currently selected tab by index + * @param index The index of the tab to select + */ + setCurrentTabIndex(index: number): void { + if (index >= 0 && index < this.tabs.length) { + this.currentIndex = index; + this.updateVisibleRange(); + this.onTabChanged.emit({ + index: this.currentIndex, + name: this.tabs[this.currentIndex], + }); + } + } + + /** + * Updates the list of tabs with new tab names + * @param tabNames The new list of tab names + */ + setTabNames(tabNames: string[]): void { + this.tabs = [...tabNames]; + + // Ensure current index is within bounds + if (this.currentIndex >= this.tabs.length) { + this.currentIndex = Math.max(0, this.tabs.length - 1); + } + + this.updateVisibleRange(); + } +} + /** * Base Window class to manage UI components */ @@ -516,7 +951,7 @@ class UIWindow { private components: UIComponent[] = []; private focusedComponentIndex = -1; - addComponent(component: UIComponent, manager: GlobalManager): void { + addComponent(component: UIComponent): void { this.components.push(component); } @@ -566,6 +1001,12 @@ class UIWindow { } } + handleTimerTrigger(event: TimerEvent) { + for (const component of this.components) { + component.handleTimerTrigger(event); + } + } + setFocus(index: number): void { // Unfocus current component if ( @@ -613,22 +1054,16 @@ class UIWindow { */ class TUIApplication { private log = new CCLog(`TUI.log`, false, DAY); - private manager: GlobalManager; private window: UIWindow; private running = false; - private keyEvent?: KeyEvent; - private charEvent?: CharEvent; constructor() { this.window = new UIWindow(); - this.manager = { - log: this.log, - }; } addComponent(component: UIComponent): void { - this.window.addComponent(component, this.manager); + this.window.addComponent(component); } run(): void { @@ -653,7 +1088,7 @@ class TUIApplication { stop(): void { this.running = false; - this.manager.log.close(); + this.log.close(); } mainLoop(): void { @@ -670,24 +1105,36 @@ class TUIApplication { keyLoop(): void { while (this.running) { // Handle input events - this.keyEvent = pullEventAs(KeyEvent, "key"); - this.manager.log.debug( - `[${TUIApplication.name}]: Get Key Event: ${textutils.serialise(this.keyEvent ?? {})}`, + const keyEvent = pullEventAs(KeyEvent, "key"); + this.log.debug( + `[${TUIApplication.name}]: Get Key Event: ${textutils.serialise(keyEvent ?? {})}`, ); - if (this.keyEvent == undefined) continue; - this.window.handleKeyInput(this.keyEvent); + if (keyEvent == undefined) continue; + this.window.handleKeyInput(keyEvent); } } charLoop(): void { while (this.running) { // Handle input events - this.charEvent = pullEventAs(CharEvent, "char"); - this.manager.log.debug( - `[${TUIApplication.name}]: Get Char Event: ${textutils.serialise(this.charEvent ?? {})}`, + const charEvent = pullEventAs(CharEvent, "char"); + this.log.debug( + `[${TUIApplication.name}]: Get Char Event: ${textutils.serialise(charEvent ?? {})}`, ); - if (this.charEvent == undefined) continue; - this.window.handleCharInput(this.charEvent); + if (charEvent == undefined) continue; + this.window.handleCharInput(charEvent); + } + } + + timerLoop(): void { + while (this.running) { + // Handle events + const timerEvent = pullEventAs(TimerEvent, "timer"); + this.log.debug( + `[${TUIApplication.name}]: Get Timer Event: ${textutils.serialise(timerEvent ?? {})}`, + ); + if (timerEvent == undefined) continue; + this.window.handleTimerTrigger(timerEvent); } } @@ -703,6 +1150,7 @@ export { TextLabel, InputField, OptionSelector, + TabWidget, UIWindow, TUIApplication, }; diff --git a/src/tuiExample/main.ts b/src/tuiExample/main.ts index cdb6a84..58fcd34 100644 --- a/src/tuiExample/main.ts +++ b/src/tuiExample/main.ts @@ -7,6 +7,7 @@ import { TextLabel, InputField, OptionSelector, + TabWidget, } from "../lib/ccTUI"; // Create the main application @@ -17,6 +18,7 @@ const [termWidth, _termHeight] = term.getSize(); // Create UI components const title = new TextLabel( + "LabelTitle", Math.floor(termWidth / 2) - 10, 2, "CC TUI Framework Demo", @@ -24,16 +26,27 @@ const title = new TextLabel( colors.black, ); -const label1 = new TextLabel(5, 5, "Enter your name:"); +const label1 = new TextLabel("Label1", 5, 5, "Enter your name:"); -const inputField = new InputField(5, 6, 30, "", "Type here..."); +const inputField = new InputField("LabelInput", 5, 6, 30, "", "Type here..."); -const optionLabel = new TextLabel(5, 8, "Select an option:"); +const optionLabel = new TextLabel("LableOption", 5, 8, "Select an option:"); const options = ["Option 1", "Option 2", "Option 3", "Option 4"]; -const optionSelector = new OptionSelector(5, 9, options, "Choose:", 0); +const optionSelector = new OptionSelector( + "OptionSelector", + 5, + 9, + options, + "Choose:", + 0, +); -const statusLabel = new TextLabel(5, 11, "Status: Ready"); +const statusLabel = new TextLabel("LableStatus", 5, 11, "Status: Ready"); + +// Create tab widget with sample tabs - using longer tab names for testing +const tabNames = ["Home", "Settings", "User Profile", "Messages", "About Us", "Documentation", "Advanced Settings", "Account Management"]; +const tabWidget = new TabWidget("TabWidget", 5, 3, 50, tabNames, 0); // Add components to the application app.addComponent(title); @@ -42,9 +55,10 @@ app.addComponent(inputField); app.addComponent(optionLabel); app.addComponent(optionSelector); app.addComponent(statusLabel); +app.addComponent(tabWidget); // Set focus to the input field initially -app.getWindow().setFocusFor(optionSelector); +app.getWindow().setFocusFor(tabWidget); // Connect events optionSelector.onSelectionChanged.connect((data) => { @@ -61,6 +75,12 @@ inputField.onTextChanged.connect((value) => { } }); +tabWidget.onTabChanged.connect((data) => { + statusLabel.setText( + `Status: Tab changed to ${data?.name} (index: ${data?.index})`, + ); +}); + // Run the application try { print("Starting CC TUI Demo. Press Ctrl+T to quit."); diff --git a/types/dkjson/index.d.ts b/thirdparty/dkjson/index.d.ts similarity index 100% rename from types/dkjson/index.d.ts rename to thirdparty/dkjson/index.d.ts diff --git a/types/dkjson/index.lua b/thirdparty/dkjson/index.lua similarity index 100% rename from types/dkjson/index.lua rename to thirdparty/dkjson/index.lua diff --git a/types/dkjson/package.json b/thirdparty/dkjson/package.json similarity index 100% rename from types/dkjson/package.json rename to thirdparty/dkjson/package.json diff --git a/types/toml2lua/index.d.ts b/thirdparty/toml2lua/index.d.ts similarity index 100% rename from types/toml2lua/index.d.ts rename to thirdparty/toml2lua/index.d.ts diff --git a/types/toml2lua/index.lua b/thirdparty/toml2lua/index.lua similarity index 100% rename from types/toml2lua/index.lua rename to thirdparty/toml2lua/index.lua diff --git a/types/toml2lua/package.json b/thirdparty/toml2lua/package.json similarity index 100% rename from types/toml2lua/package.json rename to thirdparty/toml2lua/package.json diff --git a/types/cc/package.json b/types/cc/package.json index 5c5d432..e6e0f63 100644 --- a/types/cc/package.json +++ b/types/cc/package.json @@ -1,9 +1,14 @@ { - "name": "@jackmacwindows/cc-types", - "version": "1.0.1", - "description": "TypeScript type definitions for CraftOS modules.", - "types": "index.d.ts", - "files": ["./*.d.ts", "./audio", "./image", "./shell"], - "author": "JackMacWindows", - "license": "MIT" -} \ No newline at end of file + "name": "@jackmacwindows/cc-types", + "version": "1.0.1", + "description": "TypeScript type definitions for CraftOS modules.", + "types": "index.d.ts", + "files": [ + "./*.d.ts", + "./audio", + "./image", + "./shell" + ], + "author": "JackMacWindows", + "license": "MIT" +}