csmit195

Titanium - Main Source Demo

Sep 29th, 2018
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.55 KB | None | 0 0
  1. local TERM_X, TERM_Y = term.getSize()
  2.  
  3. term.setBackgroundColour( 1 )
  4. term.clear()
  5.  
  6. local tasks = {
  7.     {"Launching Titanium"},
  8.     {"Instantiating Application"},
  9.     {"Loading TML"},
  10.     {"Loading Theme"},
  11.     {"Applying Theme"},
  12.     {"Registering callbacks"},
  13.     {"Starting parallel thread"},
  14.     {"Done"}
  15. }
  16.  
  17. local function printCentre( text, y, col )
  18.     if col then term.setTextColour( col ) end
  19.  
  20.     term.setCursorPos( math.floor( TERM_X / 2 - ( #text / 2 ) ), y )
  21.     term.clearLine()
  22.     term.write( text )
  23. end
  24.  
  25. local function completeTask( task )
  26.     for i = 1, #tasks do
  27.         if not tasks[ i ][ 2 ] then
  28.             tasks[ i ][ 2 ] = os.clock()
  29.  
  30.             local y = 9
  31.             for i = 1, #tasks do
  32.                 local done = tasks[ i ][ 2 ]
  33.                 printCentre( tasks[ i ][ 1 ] .. ( done and " ["..done.."]" or "" ), y, done and colours.green or ( pre and colours.cyan ) or 256 )
  34.  
  35.                 pre, y = done, y + 1
  36.             end
  37.  
  38.             return
  39.         end
  40.     end
  41. end
  42.  
  43. sleep( 0 )
  44. printCentre("Titanium GUI Framework", 4, colours.cyan)
  45. printCentre("Example Application", 5, colours.lightGrey)
  46. printCentre("Loading", TERM_Y - 1, 128)
  47.  
  48. dofile "dragonSystem" -- Run the compiled Titanium script
  49. completeTask()
  50.  
  51. --[[
  52.     An Application instance is the starting point of a Titanium application. It accepts 4 arguments: x, y, width and height.
  53.     The default position and size was fine for my use case, so I passed no arguments inside the brackets.
  54.  
  55.     I did however want to adjust some other properties of the Application, so for that I used the `set` function and passed a
  56.     key-value table. The key being the name of the property and the value being the value of the property.
  57.  
  58.     `:set` is available on all nodes as well and returns the object you called it on (in this case, Application) so you can chain other functions
  59.     after it.
  60. ]]
  61. Manager = Application():set {
  62.     colour = 128,
  63.     backgroundColour = 1,
  64.     terminatable = true
  65. }
  66.  
  67. completeTask()
  68.  
  69. --[[
  70.     TML is a custom markup language for Titanium aiming to drastically increase productivity and descrease the amount of Lua you write when
  71.     designing your UI.
  72.  
  73.     The import function loads the TML file and adds all the nodes generated to `Manager`, which is our Application instance.
  74. ]]
  75. Manager:importFromTML "ui/master.tml"
  76. completeTask()
  77.  
  78. --[[
  79.     This local is a table that contains some commonly used assets. `Manager:query` allows us to use CSS like selectors
  80.     to search all the nodes inside of our application and return the result.
  81.  
  82.     To speed the program up, we only query these things once and store the result in `app`.
  83. ]]
  84. local app = {
  85.     -- Here we import our theme file, this is the same sytnax as TML however it doesn't create nodes but instead allows styling (think, a CSS file).
  86.     masterTheme = Theme.fromFile( "masterTheme", "ui/master.theme" ),
  87.  
  88.     -- Grab our page container which was created in our TML file. This page container has two pages, which we will get into later
  89.     pages = Manager:query "#mainContainer".result[1],
  90.  
  91.     -- Store some commonly used assets for our animated sidebar
  92.     sidePane = {
  93.         pane = Manager:query "Container#pane",
  94.         hotkeys = Manager:query "Label.hotkey_part",
  95.  
  96.         left = Manager:query "Label#left.hotkey_part",
  97.         right = Manager:query "Label#right.hotkey_part"
  98.     },
  99. }
  100.  
  101. completeTask()
  102.  
  103. -- Using our `app` local, switch the current page to 'main'. The page named 'main' which is defined in our TML file is now visible on the screen
  104. app.pages:selectPage "main"
  105. app.pages.animationDuration = "${#animationSlider}.value * 0.15"
  106.  
  107. -- We already imported our theme file inside our `app` local, however we haven't added it to our application yet. Doing so means the theme file will be applied
  108. Manager:addTheme( app.masterTheme )
  109. completeTask()
  110.  
  111. --[[
  112.     This is the first time we have used ':on', so what exactly does it do?
  113.  
  114.     First, we query the application for something with an id of 'exit_button'. Titanium will search your application and return a
  115.     `NodeQuery` instance which we can use to access the results. Instead of accessing the results directly, we use the NodeQuery
  116.     shortcut feature to apply changes straight away.
  117.  
  118.     Calling ':on' tells Titanium to bind an event listener to all the nodes it found, in our case this is just one. We tell Titanium 'on trigger, run this function'.
  119.     Trigger means the node was... triggered. In this case, the node we got is a Button so it means when the button is clicked.
  120. ]]
  121. local exit_button = Manager:query "#exit_button"
  122. exit_button:on( "trigger", function( self )
  123.     -- Our exit button has been clicked. This means the button is enabled, and therefore the 'yes' checkbox was selected
  124.     Manager:stop()
  125. end)
  126.  
  127. --[[
  128.     These two binds will enable/disable the button depending on the one clicked.
  129.  
  130.     This means that the exit button will only become enabled when the '#yes_rating' radio button is clicked, preventing the button
  131.     from closing the program until the user 'allows exit'.
  132. ]]
  133. Manager:query "#yes_rating":on( "select", function() exit_button:set{ enabled = true } end)
  134. Manager:query "#no_rating":on( "select", function() exit_button:set{ enabled = false } end)
  135.  
  136. --[[
  137.     Much like above, we grab our node with an id 'name_display'. In our TML file, we can see that is a label - a simple node for displaying a line of text
  138.     We also query the application for a node with id 'name_input', which in our case is an 'Input' node. We bind a trigger event (when enter is pressed inside the input) to
  139.     the node and pass a function for Titanium to call when the input is triggered.
  140. ]]
  141. nameDisplay = Manager:query "#name_display"
  142. Manager:query "#name_input":on("trigger", function( self, value, selectedValue )
  143.     --[[
  144.         Input has been triggered, lets do some stuff!
  145.  
  146.         First, we set the 'hasValue' class of our name display label to true IF the user has enetered some text. If they haven't, the class is removed.
  147.         This class is used in our Theme file to change the colour of the label depending on whether or not the user entered text.
  148.     ]]
  149.     nameDisplay:setClass( "hasValue", value ~= "" )
  150.  
  151.     --[[
  152.         Next, we get the label from our node query (using .result[1], which means get the first result) and set it's text value (.text = "text value") to a value.
  153.         This value will be 'No entered text' if the value the user entered is blank (empty string). If the user did enter something we check if the value exceeds 40
  154.         characters in length.
  155.  
  156.         If it does exceed this length, we trim it down to 40 characters and stick '...' on the end to show that it has been trimmed. If the text does NOT exceed this
  157.         limit, it is simply displayed as it.
  158.     ]]
  159.     nameDisplay.result[1].text = ( not value or value == "" ) and "No entered text" or ( #value > 40 and value:sub( 1, 40 ).."..." or value )
  160.  
  161.     -- if the user selected text (shift-<arrowKey>), we display it here. If they did not, we display 'No selected text'
  162.     Manager:query "#selected_name_display".result[1].text = selectedValue and "Selected: "..selectedValue or "No selected text"
  163. end)
  164.  
  165. -- A local variable used to keep track of the current theme
  166. local themed = true
  167. Manager:query "#toggle":on("trigger", function( self )
  168.     -- When the button with id 'toggle' is clicked, toggle the theme in the application.
  169.     themed = not themed
  170.     if not themed then Manager:removeTheme "masterTheme"
  171.     else Manager:addTheme( app.masterTheme ) end
  172. end)
  173.  
  174. --[[
  175.     Here we handle the animation of the side bar. We localise `app.sidePane` to just `sidePane` for simplicities sake.
  176.  
  177.     When this function is called, it will be passed a value. This value will either be true, or something else (the instance of the application that invoked it).
  178.     If the value is true (not truthy), we know it was called from the hotkey function that runs when `ctrl-p` is activated. Because of this, we light up the hotkey
  179.     display (under the pane toggle button) to show that the hotkey worked by adding the 'active' class to all sections of the hotkey.
  180. ]]
  181. local sidePane, paneStatus = app.sidePane
  182. local function paneToggle( isKey )
  183.     paneStatus = not paneStatus
  184.  
  185.     if isKey == true then sidePane.hotkeys:addClass "active" end
  186.  
  187.     --[[
  188.         Start the animation. Depending on whether or not the pane is animating out or in, different values are passed.
  189.  
  190.         If the pane is animating into view, the target X is 31, with a duration of 0.15s and an easing of 'outSine'.
  191.         If the pane is animating out of view, the target X is 52, with a duration of 0.2s and an easing of 'inQuad'
  192.     ]]
  193.     sidePane.pane:animate("sidePaneAnimation", "X", paneStatus and 31 or 52, paneStatus and 0.15 or 0.2, paneStatus and "outSine" or "inQuad", function()
  194.         -- This function is run when the animation is complete. We remove the highlight on the hotkey combination to return it back to normal colour
  195.         sidePane.hotkeys:removeClass "active"
  196.     end)
  197. end
  198.  
  199.  
  200. -- The 'Save Settings' button was clicked inside the side bar. Close the sidebar by calling `paneToggle`
  201. Manager:query"#config_save":on("trigger", function( self )
  202.     if paneStatus then
  203.         paneToggle()
  204.     end
  205. end)
  206.  
  207. -- The 'Shell' or 'Return' button was clicked (depending on the selected page). Swap the page.
  208. -- When the page swaps, different content will be displayed. This is defined in the TML file using `<Page name="main">` and `<Page name="console">`
  209. Manager:query ".page_change":on("trigger", function( self )
  210.     app.pages:selectPage( self.targetPage )
  211. end)
  212.  
  213. Manager:registerHotkey("close", "leftCtrl-leftShift-t", Manager.stop) -- Setup a hotkey that makes closing the program quicker
  214. Manager:query "#pane_toggle":on("trigger", paneToggle) -- When the pane toggle button is clicked... toggle the pane
  215. Manager:registerHotkey("paneToggle", "leftCtrl-p", function()
  216.     -- If the hotkey to toggle the pane was clicked, toggle it and pass the 'true' value (see the comments above function paneToggle)
  217.     paneToggle( true )
  218. end)
  219.  
  220. completeTask()
  221.  
  222. --[[
  223.     This thread runs alongside the Application thread, allowing parallel programming from inside Titanium.
  224.  
  225.     This thread sets a loop that yields for events. When an event is received, it is a Titanium event. We check if the event is a 'key' event.
  226.     If the key was pressed down we highlight that part of the hotkey by adding the held class to the label.
  227.  
  228.     If the key was released, we remove the class. The Theme file uses this theme to highlight the label when the 'held' class is present
  229. ]]
  230. Manager:addThread(Thread(function()
  231.     while true do -- Wait for events indefinitely
  232.         local event = coroutine.yield() -- Wait for events
  233.         if event.main == "KEY" then -- Is it a key event? (key, key_up)
  234.             if event.keyName == "leftCtrl" then -- The user pressed OR released the 'leftCtrl' key
  235.                 sidePane.left:setClass( "held", event.sub == "DOWN" ) -- Set the class 'held' on the 'leftCtrl' label if the user pressed the key. Otherwise, remove the class
  236.             elseif event.keyName == "p" then -- The user pressed OR released the 'p' key
  237.                 sidePane.right:setClass( "held", event.sub == "DOWN" ) -- Set the class 'held' on the 'p' label if the user pressed the key. Otherwise, remove the class
  238.             end
  239.         end
  240.     end
  241. end, true)) -- This true value tells Titanium to give the thread Titanium events, NOT CC events.
  242.  
  243. completeTask()
  244.  
  245. -- On the second page of the Application we have a terminal node that can be accessed by pressing the 'Shell' button in the top right.
  246. -- We start the terminal by loading the '/rom/programs/shell' program and setting that as the terminal 'chunk'. The terminal node is now running
  247. -- the shell and we can use it just like a normal shell
  248. Manager:query "Terminal#shell":set { chunk = function() select( 1, loadfile "/rom/programs/shell" )() end }
  249.  
  250. --[[
  251.     Another way of waiting for events is using the ':on' function. Simply provide the CC event name (mouse_click, key_up, char, paste) as the first argument
  252.     and a function to run as the second.
  253.  
  254.     In this function we spawn an example context menu when the user right clicks on empty space. If the user right clicks on a node that uses the
  255.     click event, the `event.handled` value will be true.
  256. ]]
  257. Manager:on("mouse_click", function( self, event )
  258.     if event.button == 2 and not event.handled then -- Only proceed if the button used was the right click, and the event wasn't used by any nodes
  259.         event.handled = true
  260.         if context then
  261.             Manager:removeNode( context )
  262.         end
  263.         context = Manager:addNode( ContextMenu({
  264.             {"button", "Copy", function( self ) error( "Button: " .. self.text) end},
  265.             {"button", "Paste", function( self ) error( "Button: " .. self.text) end},
  266.             {"rule"},
  267.             {"menu", "More \16", {
  268.                 {"button", "Move", function( self ) error( "Button: " .. self.text) end},
  269.                 {"button", "Delete", function( self ) error( "Button: " .. self.text) end},
  270.                 {"menu", "More \16", {
  271.                     {"button", "Rename", function( self ) error( "Button: " .. self.text) end},
  272.                     {"button", "Remove", function( self ) error( "Button: " .. self.text) end},
  273.                 }}
  274.             }}
  275.         }, event.X, event.Y ):set{ backgroundColour = colours.yellow, colour = 128 } )
  276.     end
  277. end)
  278.  
  279. -- We are ready to go. Any code after this function will not be executed until the application closes.
  280. completeTask()
  281. Manager:start()
Add Comment
Please, Sign In to add comment