An advanced UI framework for ComputerCraft written in TypeScript, targeting both Phoenix and CraftOS systems.
Instructions vary depending on what OS and language you're using.
These steps only apply to users who installed the "minimal" OS without a desktop. Desktop users already have everything installed.
Install the cckit2
package through apt
:
sudo apt install cckit2
You will also need a window manager that's compatible with CCKit2.
For Hydra support, run sudo apt install hydra cckit2-wm-hydra
. For basic framebuffer support, run sudo apt install cckit2-wm-fb
.
To install system-wide, run pastebin run
. This will place CCKit2's files in /CCKit2
. Note that applications in a folder will need to have CCKit2
copied inside to work properly.
Alternatively, download and extract the latest build of CCKit2 for CraftOS from the Releases page. This will include the CraftOS window manager with it, so it won't need any extra fiddling to work. Then copy the CCKit2
folder into the root of the computer.
If you prefer to build from source:
npm install
in each to get the required packages.npm run build
in each to build the library.bin/CCKit2
in each repo to the computer, merging the WM files into the core library./usr/lib/CCKit2
. The files here will override the default package in /usr/lib/libCCKit2.a
./CCKit2
.typescript
package (sudo apt install typescript
), or copy bin/typescript.lua
to /usr/lib
.bin/typescript.lua
to /typescript.lua
.TypeScript is the preferred language for CCKit2, as it provides strong typing and syntax features that help keep your code clean and stable.
The quickest way to create a new TypeScript project is to clone the template repo. This will have all of the required configuration ready to go. After downloading it, open the folder in the terminal and run npm install
to install the dependencies. Then open package.json
and replace the placeholder text with info about your project (recommended if publishing on GitHub).
Alternatively, you may set it up manually:
npm init
to create a new project, and answer each question. When it asks for an entry point, give it src/index.ts
(or your app's main program).npm install --save-dev @jackmacwindows/typescript-to-lua @jackmacwindows/lua-types @phoenix-cc/cckit2
to install TSTL and CCKit2's typings.npm install --save-dev @phoenix-cc/libsystem
.npm install --save-dev @jackmacwindows/craftos-types
.tsconfig.json
, and insert the following contents:{
"$schema": "https://raw.githubusercontent.com/MCJack123/TypeScriptToLua/master/tsconfig-schema.json",
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext"],
"moduleResolution": "node",
"strict": true,
"typeRoots": ["./node_modules"],
"types": ["@jackmacwindows/lua-types/cc", "@phoenix-cc/cckit2"],
"baseUrl": "./src",
"rootDir": "src",
"outDir": "bin",
"declaration": true,
"removeComments": false
},
"tstl": {
"luaTarget": "CC",
"luaLibImport": "require",
"luaLibName": "typescript"
},
"include": [
"./src/*.ts"
]
}
src
, and create a file named index.ts
inside. This will serve as your app's main file.Lua projects are not recommended because they don't have the same safety guarantees as TypeScript, and may require some more verbose code with wrappers to accomplish certain tasks. However, it remains an option for users who don't know or simply wish to avoid TypeScript.
Creating a Lua project is as simple as making a single Lua file for the app. Place the following code at the top of every Lua file you create, as it loads important compatibility wrappers for Lua projects:
local LuaWrappers = require "CCKit2.LuaWrappers"
When loading CCKit2 classes, use LuaWrappers.import
instead of require
, as it returns a more manageable module format than the one that TypeScript exports.
To create a class, use local MyClass = LuaWrappers.class("MyClass", super, fields)
. The second argument is the base class to extend from (nil
if there is none), and the third is a table of methods and fields in the class. The fields table holds methods of the class directly, as well as descriptors (defined as tables with a get
and optional set
function inside), and default values to set for certain properties. The ____constructor
field is the constructor for the class. All methods must take a self
parameter before other parameters.
To construct an instance of a class, call LuaWrappers.new
with the class + any arguments to pass to the constructor. If you want to check if an object is an instance of a class, call LuaWrappers.instanceOf(obj, class)
.
Here's a barebones template program to demonstrate the structure of a CCKit2 application.
import CCApplication from "CCKit2/CCApplication";
import CCApplicationDelegate from "CCKit2/CCApplicationDelegate";
import CCApplicationMain from "CCKit2/CCApplicationMain";
import CCDefaultWindowManagerConnection from "CCKit2/CCDefaultWindowManagerConnection";
import CCViewController from "CCKit2/CCViewController";
class ViewController extends CCViewController {
public viewDidLoad(): void {
super.viewDidLoad();
// Add views to the view controller here.
// let myView = new CCLabel({x: 1, y: 1}, "Hello World!");
// this.view.addSubview(myView);
}
}
class AppDelegate implements CCApplicationDelegate {
applicationWindowManagerConnection(app: CCApplication): CCWindowManagerConnection {
return CCDefaultWindowManagerConnection(app);
}
}
CCApplicationMain(ViewController, new AppDelegate());
local LuaWrappers = require "CCKit2.LuaWrappers"
local CCApplicationMain = LuaWrappers.import "CCApplicationMain"
local CCDefaultWindowManagerConnection = LuaWrappers.import "CCDefaultWindowManagerConnection"
local CCViewController = LuaWrappers.import "CCViewController"
local ViewController = LuaWrappers.class("ViewController", CCViewController, {
viewDidLoad = function(self)
CCViewController.prototype.viewDidLoad(self)
-- Add views to the view controller here.
-- local myView = LuaWrappers.new(CCLabel, {x = 1, y = 1}, "Hello World!")
-- self.view:addSubview(myView)
end
})
local AppDelegate = LuaWrappers.class("AppDelegate", nil, {
applicationWindowManagerConnection = function(self, app)
return CCDefaultWindowManagerConnection(nil, app)
end
})
CCApplicationMain(nil, ViewController, LuaWrappers.new(AppDelegate))
Read on to learn what each of these parts does.
CCKit2 follows the model-view-controller (MVC) paradigm for organization. This refers to the three categories of core types used in CCKit2:
CCView
.CCViewController
class.Developers who have used Apple's frameworks for macOS and iOS (AppKit/Cocoa and UIKit, respectively) will recognize the feel of CCKit2, which is heavily inspired by these libraries' API design.
Views are the core elements in a UI. CCKit2 provides many different view types - some display information, some take input from a user, and some act as a container for other views. All views inherit from the CCView
base class, which can also be instantiated by itself like any other view type.
Views are created by constructing the class for that specific view type, usually receiving a frame or position in the process, along with other required arguments:
let myView = new CCView({x: 1, y: 1, width: 10, height: 5});
let myLabel = new CCLabel({x: 4, y: 5}, "Label");
local myView = LuaWrappers.new(CCView, {x = 1, y = 1, width = 10, height = 5})
local myLabel = LuaWrappers.new(CCLabel, {x = 4, y = 5}, "Label")
Views must be added to the view hierarchy to be rendered on screen. This means that views won't automatically appear on screen - they need a parent to go under, which must also have a parent up until the root view inside a view controller. Call the addSubview(view: CCView)
method on any view to add it as a child of that view. For views placed directly onto a window, use the view
property of the view controller. Inside the viewDidLoad
method of a view controller (more about that below):
this.view.addSubview(myView);
this.view.addSubview(myLabel);
self.view:addSubview(myView)
self.view:addSubview(myLabel)
Properties of a view may be edited and updated in real-time without requiring the app to request a redraw. For example, simply changing the text
property of a label will update the text on-screen immediately:
myLabel.text = "New text"; // will appear immediately
myLabel.text = "New text" -- will appear immediately
(Note: Custom view types will need to use getters/setters which call the setNeedsDisplay
method to make this happen. All built-in views have this functionality implemented.)
A view controller is a class that is responsible for bringing up and taking down views, and responding to events from views. They are usually rooted inside a window, but view controllers may also be placed inside other view controllers.
View controllers define "scenes" for an application, creating the views inside the window and laying them out properly. The application starts with a single view controller class that defines the contents of the root window, which is passed to the CCApplicationMain
entrypoint. More view controllers may be presented either by calling the present
method on a window, which replaces the window's view controller with a new one, or by creating a new window with a new view controller.
View controllers can catch many lifecycle events, such as the root view appearing and disappearing, but by far the most important event is the viewDidLoad
method. This method is called when the view controller's view is fully loaded, and is an app's chance to add in its own views.
To create a view controller, simply create a class which extends from CCViewController
:
class MyViewController extends CCViewController {
}
local MyViewController = LuaWrappers.class("MyViewController", CCViewController, {
})
To catch an event, such as viewDidLoad
, override that method in the view controller, making sure to call the super
implementation in the process:
public viewDidLoad(): void {
super.viewDidLoad();
}
viewDidLoad = function(self)
CCViewController.prototype.viewDidLoad(self)
end,
The CCApplicationMain
function must be called at the end of the main program file to start the application. Prior set-up may be done to e.g. parse command-line arguments, but CCApplicationMain
must come after. This function takes two parameters: the class which defines the first view controller, and an object which implements the CCApplicationDelegate
interface.
When CCApplicationMain
runs, it creates a new instance of the CCApplication
class. This class is used to handle all runtime event handling and distribution. To allow CCKit2 to run on many different operating systems, CCApplication
depends on an adapter interface which connects CCKit2 to the system. This interface is called CCWindowManagerConnection
, and can be implemented in different ways for different systems.
To pick the right CCWindowManagerConnection
instance, CCKit2 asks the app to select one, through the CCApplicationDelegate
interface which is passed to CCApplicationMain
. For most purposes, calling the CCDefaultWindowManagerConnection
function is the most appropriate, which picks the right one for the current platform, but applications have the option to override this.
CCApplicationDelegate
can also implement optional methods to receive application-wide events, such as when the app is about to finish launching or exit.
The application delegate class in the skeleton above is appropriate for most applications, but it can be extended to receive those events too.
The core CCKit2 framework and CraftOS window manager are licensed under the GNU General Public License, version 2 or later. The CCWindowManagerConnection
implementations for Phoenix are licensed under the same license as Phoenix, which is the Phoenix EULA at this time.