Compare commits

..

No commits in common. "28ed81c357bbb084cd60eca7f73349d14a0b3a20" and "afd690f59825579d30b47da7c8069e8ab5e32124" have entirely different histories.

46 changed files with 5470 additions and 601 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020- teverse.com
Copyright (c) 2018 teverse.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -3,8 +3,6 @@
This repo contains open source components produced for Teverse.
Contains the redesigned components of Workshop (``./workshop``).
# Contributing to Teverse

View File

@ -1 +0,0 @@
Fully Redesigned Workshop for TevX.

View File

@ -0,0 +1,76 @@
local keybinder = require("tevgit:workshop/controllers/core/keybinder.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local clipboard = {}
keybinder:bind({
name = "copy",
key = enums.key.c,
priorKey = enums.key.leftCtrl,
action = function()
clipboard = selection.selection
end
})
keybinder:bind({
name = "paste",
key = enums.key.v,
priorKey = enums.key.leftCtrl,
action = function()
history.beginAction(workspace, "Paste")
local bounds = aabb()
if #clipboard > 0 then
bounds.min = clipboard[1].position
bounds.max = clipboard[1].position
end
-- translates the pasted objects by:
local offset = vector3(0, 0, 0)
-- pass the clipboard to calculate bounds
for _,v in pairs(clipboard) do
if type(v.position) == "vector3" and type(v.size) == "vector3" then
bounds:expand(v.position + (v.size / 2))
bounds:expand(v.position - (v.size / 2))
end
end
offset = vector3(0, bounds.max.y - bounds.min.y, 0)
local centre = bounds:getCentre()
local clones = {}
for _,v in pairs(clipboard) do
if v and v.alive then
local new = v:clone()
new.parent = workspace
if type(new.position) == "vector3" then
new.position = new.position + offset
end
table.insert(clones, new)
end
end
history.endAction()
selection.setSelection(clones)
end
})
keybinder:bind({
name = "duplicate",
key = enums.key.d,
priorKey = enums.key.leftCtrl,
action = function()
history.beginAction(workspace, "Duplicate")
local clones = {}
for _,v in pairs(selection.selection) do
if v and v.alive then
local new = v:clone()
new.parent = workspace
table.insert(clones, new)
end
end
history.endAction()
selection.setSelection(clones)
end
})

View File

@ -0,0 +1,307 @@
--[[
The history ui window is managed in another module
Example Usuage:
local history = require(thismodule)
local selection = {obj1, obj2}
history.beginAction(selection, "testAction")
obj1.colour = colour:random()
something(obj2)
history.endAction()
]]
local limit = 100 -- how many actions can we store
local shared = require("tevgit:workshop/controllers/shared.lua")
local pointer = limit
local actions = {}
-- used internally to prevent unfinished actions
local actionInProgress = false
local callback = nil
-- Used to track changes during an action
local changes = {}
local destroyedObjects = {}
local newObjects = {}
local eventListeners = {}
local actionName = ""
local changedListener, ChildAddedListener, destroyingListener;
local function registerEvents(object)
table.insert(eventListeners, object:onSync("changed", changedListener))
table.insert(eventListeners, object:onSync("childAdded", ChildAddedListener))
table.insert(eventListeners, object:onSync("destroying", destroyingListener))
end
-- oldValue added to changed event from "POTATO 0.7.0"
changedListener = function (property, value, oldValue)
local changedObject = self.object
if not changes[changedObject] then
changes[changedObject] = {}
end
if not changes[changedObject][property] then
changes[changedObject][property] = {oldValue, value}
else
-- do not change the old value, we've already recorded it.
changes[changedObject][property][2] = value
end
end
destroyingListener = function()
local changedObject = self.object
if not changes[changedObject] then
changes[changedObject] = {}
end
-- Object is being destroyed, let's save a copy of all their writable properties so the user can undo this action
local members = shared.workshop:getMembersOfObject( changedObject )
local toStore = {}
for _, prop in pairs(members) do
local val = changedObject[prop.property]
local pType = type(val)
if prop.writable and pType ~= "function" then
-- We can save it and re-construct it
toStore[prop.property] = val
end
end
toStore["parent"] = changedObject.parent
toStore["className"] = changedObject.className
toStore["_ref"] = changedObject
table.insert(destroyedObjects, toStore)
end
ChildAddedListener = function(child)
local changedObject = child
if not changes[changedObject] then
changes[changedObject] = {}
end
-- Object is being destroyed, let's save a copy of all their writable properties so the user can undo this action
local members = shared.workshop:getMembersOfObject( changedObject )
local toStore = {}
for _, prop in pairs(members) do
local val = changedObject[prop.property]
local pType = type(val)
if prop.writable and pType ~= "function" then
-- We can save it and re-construct it
toStore[prop.property] = val
end
end
toStore["parent"] = changedObject.parent
toStore["className"] = changedObject.className
toStore["_ref"] = changedObject
registerEvents(changedObject)
table.insert(newObjects, toStore)
end
local function count(dictionary)
local i = 0
for _,v in pairs(dictionary) do i = i + 1 end
return i
end
-- Tell this module that we're about to change some things
-- the module will register changed callbacks to record the before/after
--
-- object : table of teverse objects or teverse object
-- name : name of the event/action
--
-- you need to call endAction after completing your changes to the objects
local function beginAction( object, name )
assert(not actionInProgress, "please use endAction before starting another")
actions[pointer+1] = nil
actionInProgress = true
actionName = name or ""
if type(object) == "table" then
for _,v in pairs(object) do
registerEvents(v)
end
else
registerEvents(object)
end
end
local function endAction()
assert(actionInProgress, "you must call beginAction first")
-- stop listening to the objects
for _,v in pairs(eventListeners) do
v:disconnect()
end
eventListeners = {}
-- if nothing changed dont create an action
if count(changes) > 0 or count(destroyedObjects) > 0 or count(newObjects) > 0 then
pointer = pointer + 1
if pointer >= limit then
actions[pointer - limit] = nil
end
actions[pointer] = {os.time(), actionName, changes, destroyedObjects, newObjects}
changes = {}
destroyedObjects = {}
newObjects = {}
if type(callback) == "function" then
callback()
end
end
actionInProgress = false
end
local function updateReferences(old, new)
for p, a in pairs(actions) do
local newTbl = {}
for ref, props in pairs(a[3]) do
if ref ~= old then
newTbl[ref] = props
else
newTbl[new] = props
end
end
a[3] = newTbl
end
end
local function undo()
if actions[pointer] ~= nil then
-- destroyed objects (restore)
for _, properties in pairs(actions[pointer][4]) do
local obj = engine[properties["className"]]()
for property, value in pairs(properties) do
obj[property] = value
end
local oldRef = properties["_ref"]
properties["_ref"] = obj
updateReferences(oldRef, obj)
end
-- created objects (destroy)
for _,properties in pairs(actions[pointer][5]) do
if properties["_ref"].alive then
properties["_ref"]:destroy()
end
end
for object, properties in pairs(actions[pointer][3]) do
if object and object.alive then
for property, values in pairs(properties) do
--values[1] = original value
--values[2] = changed value
object[property] = values[1]
end
else
for k,v in pairs(properties) do print(k,v) end
warn("There was a change recorded, but we couldn't find the object.")
end
end
pointer = pointer - 1
if type(callback) == "function" then
callback()
end
else
print("nothing to undo")
end
end
local function redo()
if actions[pointer + 1] ~= nil then
pointer = pointer + 1
-- destroyed objects (destroy)
for _, properties in pairs(actions[pointer][4]) do
if properties["_ref"].alive then
properties["_ref"]:destroy()
end
end
-- created objects (create)
for _,properties in pairs(actions[pointer][5]) do
local obj = engine[properties["className"]]()
for property, value in pairs(properties) do
obj[property] = value
end
local oldRef = properties["_ref"]
properties["_ref"] = obj
updateReferences(oldRef, obj)
end
for object, properties in pairs(actions[pointer][3]) do
if object and object.alive then
for property, values in pairs(properties) do
--values[1] = original value
--values[2] = changed value
object[property] = values[2]
end
else
warn("There was a change recorded, but we couldn't find the object.")
end
end
if type(callback) == "function" then
callback()
end
else
print("nothing to redo")
end
end
local keybinder = require("tevgit:workshop/controllers/core/keybinder.lua")
keybinder:bind({
name = "undo",
priorKey = enums.key.leftCtrl,
key = enums.key.z,
action = function ()
if not engine.input:isKeyDown(enums.key.leftShift) then
undo()
else
redo()
end
end
})
keybinder:bind({
name = "redo",
priorKey = enums.key.leftCtrl,
key = enums.key.y,
action = redo
})
return {
beginAction = beginAction,
endAction = endAction,
getActions = function() return actions end,
getPointer = function() return pointer end,
limit = limit,
setCallback = function (cb)
callback = cb
end,
undo = undo,
redo = redo,
count = count
}

View File

@ -0,0 +1,59 @@
-- Copyright (c) 2020 teverse.com
local hotkeysController = {
bindings = {}
}
function hotkeysController:bind(hotkeyData)
if (hotkeyData.priorKey) then
if (not self.bindings[hotkeyData.priorKey]) then
self.bindings[hotkeyData.priorKey] = {}
end
if (self.bindings[hotkeyData.priorKey][hotkeyData.key]) then
error("Hot key " .. hotkeyData.name .. " can not overwrite existing hotkey: " .. self.bindings[hotkeyData.priorKey][hotkeyData.key].name)
end
self.bindings[hotkeyData.priorKey][hotkeyData.key] = hotkeyData
else
if (self.bindings[hotkeyData.key]) then
error("Hot key " .. hotkeyData.name .. " can not overwrite existing hotkey: " .. self.bindings[hotkeyData.key].name)
end
self.bindings[hotkeyData.key] = hotkeyData
end
return hotkeyData.action
end
function hotkeysController:handle(inputObject)
for key, data in pairs(self.bindings) do
if (not data.action) then
if (engine.input:isKeyDown(key)) then
for key, data in pairs(data) do
if (inputObject.key == key) then
data.action()
return
end
end
end
end
end
-- bad: seperate pass because we prioritise prior key commands.
for key, data in pairs(self.bindings) do
if (data.action) then
if (inputObject.key == key) then
data.action()
return
end
end
end
end
engine.input:keyPressed(function(inputObject)
if (inputObject.systemHandled) then
return
else
hotkeysController:handle(inputObject)
end
end)
return hotkeysController

View File

@ -0,0 +1,39 @@
-- This file manages lights!
-- It creates a 3d mesh for each light in the scene
-- this is so the developer can easily interact with their lights in the 3d scene...
local controller = {}
controller.lights = {}
local function processChild(child)
if type(child) == "light" then
local block = engine.construct("block", workspace, {
size = vector3(0.5, 0.5, 0.5),
position = child.position,
rotation = child.rotation:inverse(),
workshopLocked = true,
doNotSerialise = true,
name = "_CreateMode_",
mesh = "tevurl:3d/light.glb"
})
child:on("changed", function(p, v)
if p == "position" then
block[p] = v
elseif p == "rotation" then
block[p] = v:inverse()
end
end)
controller.lights[block] = child
child:onSync("destroying", function()
controller.lights[block] = nil
block:destroy()
end)
end
end
workspace:on("childAdded", processChild)
return controller

View File

@ -0,0 +1,206 @@
-- Selection Controller is incharge of storing and managing the users selection
local controller = {}
local boundingBox = engine.construct("block", workspace, {
name = "_bounding",
wireframe = true,
static = true,
physics = false,
workshopLocked = true,
doNotSerialise = true,
position = vector3(0, -100, 0)
})
controller.selection = {}
controller.destroyingListeners = {}
controller.callbacks = {}
controller.fireCallbacks = function ()
for _,v in pairs(controller.callbacks) do
v()
end
end
controller.registerCallback = function (cb)
table.insert(controller.callbacks, cb)
end
controller.setSelection = function(obj)
controller.selection = {}
for _,v in pairs(controller.destroyingListeners) do
v:disconnect()
end
controller.destroyingListeners = {}
controller.addSelection(obj)
end
local function destroyListener()
for i,v in pairs(controller.selection) do
if v == self.object then
table.remove(controller.selection, i)
break
end
end
controller.destroyingListeners[self.object] = nil
self:disconnect()
end
controller.addSelection = function(obj)
if type(obj) == "table" then
for _,v in pairs(obj) do
if v.isA and v:isA("baseClass") and not v.workshopLocked then
table.insert(controller.selection, v)
controller.destroyingListeners[v] = v:once("destroying", destroyListener)
else
warn("selecting unknown object")
end
end
else
if obj.isA and obj:isA("baseClass") then
table.insert(controller.selection, obj)
controller.destroyingListeners[obj] = obj:once("destroying", destroyListener)
else
warn("selecting unknown object")
end
end
controller.fireCallbacks()
end
controller.isSelected = function(obj)
for _,v in pairs(controller.selection) do
if v == obj then
return true
end
end
end
controller.hasSelection = function()
return #controller.selection > 0
end
local boundingEvents = {}
local function boundUpdate()
if not boundingBox or not boundingBox.alive then return end
--inefficient, is called for each change
local bounds = aabb()
if #controller.selection > 0 and controller.selection[1].position then
bounds.min = controller.selection[1].position
bounds.max = controller.selection[1].position
for _,v in pairs(controller.selection) do
if type(v) == "block" then
bounds:expand(v)
elseif type(v) == "light" then
bounds:expand(v.position - vector3(0.25, 0.25, 0.25))
bounds:expand(v.position + vector3(0.25, 0.25, 0.25))
elseif type(v.position) == "vector3" then
bounds:expand(v.position)
end
end
end
boundingBox.position = bounds:getCentre()
boundingBox.size = bounds.max - bounds.min
end
-- on selection changed
controller.registerCallback(function()
for _,v in pairs(boundingEvents) do
v:disconnect()
end
boundingEvents = {}
if not boundingBox or not boundingBox.alive then return end
local bounds = aabb()
if #controller.selection > 0 and type(controller.selection[1].position) == "vector3" then
bounds.min = controller.selection[1].position
bounds.max = controller.selection[1].position
for _,v in pairs(controller.selection) do
if type(v.position) == "vector3" then
if type(v) == "block" then
bounds:expand(v)
elseif type(v) == "light" then
bounds:expand(v.position - vector3(0.25, 0.25, 0.25))
bounds:expand(v.position + vector3(0.25, 0.25, 0.25))
elseif type(v.position) == "vector3" then
bounds:expand(v.position)
end
table.insert(boundingEvents, v:changed(boundUpdate))
end
end
end
boundingBox.position = bounds:getCentre()
boundingBox.size = bounds.max - bounds.min
end)
local keybinder = require("tevgit:workshop/controllers/core/keybinder.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
keybinder:bind({
name = "delete",
key = enums.key.delete,
action = function()
history.beginAction(controller.selection, "Delete")
for _,v in pairs(controller.selection) do
v:destroy()
end
history.endAction()
end
})
keybinder:bind({
name = "select all",
key = enums.key.a,
priorKey = enums.key.leftCtrl,
action = function()
local children = workspace.children
for i,v in pairs(children) do
if v:isA("camera") then
table.remove(children, i)
end
end
controller.setSelection(children)
end
})
keybinder:bind({
name = "focus",
key = enums.key.f,
action = function()
-- calculate the 'centre' of the selection
local bounds = aabb()
if #controller.selection > 0 then
bounds.min = controller.selection[1].position
bounds.max = controller.selection[1].position
end
for _,v in pairs(controller.selection) do
local size = v.size and v.size or vector3(0.1, 0.1, 0.1)
bounds:expand(v.position + (size/2))
bounds:expand(v.position - (size/2))
end
local centre = bounds:getCentre()
workspace.camera:lookAt(centre)
end
})
controller.box = boundingBox
return controller

View File

@ -0,0 +1,74 @@
-- Copyright 2020 teverse.com
local cameraController = {
zoomStep = 3,
rotateStep = 0.003,
moveStep = 0.5,
slow = 0.1
}
cameraController.camera = workspace.camera
-- Setup the initial position of the camera
cameraController.camera.position = vector3(11, 5, 10)
cameraController.camera:lookAt(vector3(0,0,0))
-- Camera key input values
cameraController.cameraKeyEventLooping = false
cameraController.cameraKeyArray = {
[enums.key.w] = vector3(0, 0, 1),
[enums.key.s] = vector3(0, 0, -1),
[enums.key.a] = vector3(-1, 0, 0),
[enums.key.d] = vector3(1, 0, 0),
[enums.key.q] = vector3(0, -1, 0),
[enums.key.e] = vector3(0, 1, 0)
}
engine.input:mouseScrolled(function( input )
if input.systemHandled then return end
local cameraPos = cameraController.camera.position
cameraPos = cameraPos + (cameraController.camera.rotation * (cameraController.cameraKeyArray[enums.key.w] * input.movement.z * cameraController.zoomStep))
cameraController.camera.position = cameraPos
end)
engine.input:mouseMoved(function( input )
if engine.input:isMouseButtonDown( enums.mouseButton.right ) then
local pitch = quaternion():setEuler(input.movement.y * cameraController.rotateStep, 0, 0)
local yaw = quaternion():setEuler(0, input.movement.x * cameraController.rotateStep, 0)
-- Applied seperately to avoid camera flipping on the wrong axis.
cameraController.camera.rotation = yaw * cameraController.camera.rotation;
cameraController.camera.rotation = cameraController.camera.rotation * pitch
--updatePosition()
end
end)
engine.input:keyPressed(function( inputObj )
if inputObj.systemHandled or engine.input:isKeyDown(enums.key.leftCtrl) then return end
if cameraController.cameraKeyArray[inputObj.key] and not cameraController.cameraKeyEventLooping then
cameraController.cameraKeyEventLooping = true
repeat
local cameraPos = cameraController.camera.position
for key, vector in pairs(cameraController.cameraKeyArray) do
-- check this key is pressed (still)
if engine.input:isKeyDown(key) then
cameraPos = cameraPos + (cameraController.camera.rotation * vector * cameraController.moveStep) * (engine.input:isKeyDown(enums.key.leftShift) and cameraController.slow or 1)
end
end
cameraController.cameraKeyEventLooping = (cameraPos ~= cameraController.camera.position)
cameraController.camera.position = cameraPos
wait(0.001)
until not cameraController.cameraKeyEventLooping
end
end)
return cameraController

View File

@ -0,0 +1,91 @@
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
return {
camera = require("tevgit:workshop/controllers/environment/camera.lua"),
createDefaultMap = function ()
local mainLight = engine.construct("light", workspace, {
name = "mainLight",
position = vector3(3, 2, 0),
type = enums.lightType.directional,
rotation = quaternion():setEuler(math.rad(80), 0, 0),
})
local basePlate = engine.construct("block", workspace, {
name = "basePlate",
colour = colour(0.9, 0.9, 0.9),
size = vector3(100, 1, 100),
position = vector3(0, -1, 0),
workshopLocked = true
})
engine.construct("block", workspace, {
name = "redBlock",
colour = colour(1, 0, 0),
size = vector3(1, 1, 1),
position = vector3(0, 0, 0)
})
engine.construct("block", workspace, {
name = "greenBlock",
colour = colour(0, 1, 0),
size = vector3(1, 1, 1),
position = vector3(1, 0, 0),
mesh = "primitive:wedge",
rotation = quaternion:setEuler(0, math.rad(90), 0)
})
end,
setupEnvironment = function ()
engine.graphics.clearColour = colour:fromRGB(56,56,66)
engine.graphics.ambientColour = colour(0.3, 0.3, 0.3)
end,
createPBRDebugSpheres = function ()
for r = 0, 1, 0.1 do
for m = 0, 1, 0.1 do
local b = engine.construct("block", workspace, {
size = vector3(0.8, 0.8, 0.8),
position = vector3((r*10)-5, 3, (m*10)-5),
mesh = "primitive:sphere",
roughness = r,
metalness = m
})
end
end
for r = 0, 1, 0.1 do
for m = 0, 1, 0.1 do
local b = engine.construct("block", workspace, {
size = vector3(10, 1, 10),
position = vector3((r*100)-5, 1, (m*100)-5),
roughness = r,
metalness = m
})
end
end
local cubes = {}
for r = 0, 1, 0.1 do
for m = 0, 1, 0.1 do
table.insert(cubes, engine.construct("block", workspace, {
size = vector3(0.5, 0.5, 0.5),
position = vector3((r*10)+10, 3, (m*10)-5),
mesh = "tevurl:3d/duck.glb",
roughness = r,
metalness = m,
colour = colour(1,1,0)
}))
end
end
spawnThread(function()
while wait() do
for _,v in pairs(cubes) do
v.rotation = v.rotation * quaternion:setEuler(0, math.rad(v.roughness*3), math.rad(v.metalness*3))
end
end
end)
end
}

View File

@ -0,0 +1,71 @@
-- Copyright 2020 Teverse.com
-- Used to share variables between scripts
-- shamelessly stolen from http://lua-users.org/wiki/SimpleRound
local function round(n, mult)
mult = mult or 1
return math.floor((n + mult/2)/mult) * mult
end
local function calculateVertices (block)
local vertices = {}
for x = -1,1,2 do
for y = -1,1,2 do
for z = -1,1,2 do
table.insert(vertices, block.position + block.rotation* (vector3(x,y,z) *block.size/2))
end
end
end
return vertices
end
local faces = {
{5, 6, 8, 7}, -- x forward,
{1, 2, 4, 3}, -- x backward,
{7, 8, 4, 3}, -- y upward,
{5, 6, 2, 1}, -- y downward,
{6, 2, 4, 8}, -- z forward
{5, 1, 3, 7} -- z backward
}
return {
-- Storing workshop is important because sandbox access is restricted.
workshop = nil,
controllers = {},
windows = {},
round = round,
roundVector3 = function(v, mult)
return vector3(round(v.x, mult), round(v.y, mult), round(v.z, mult))
end,
roundDp = function (num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end,
calculateVertices = calculateVertices,
getCentreOfFace = function (block, face)
local vertices = calculateVertices(block)
local avg = vector3(0,0,0)
for _,i in pairs(faces[face]) do
avg = avg + vertices[i]
end
return avg/4
end,
--[[guiLine = function(a, b, parent)
local avg = (a + b) / 2
local size = a - b
local length = size:length()
return engine.construct("guiFrame", parent, {
position = guiCoord(0, avg.x, 0, avg.y),
size = guiCoord(0, 2, 0, length)
})
end,]]
-- this is set when workshop is set
-- using the haslocaltevgit api
developerMode = false
}

View File

@ -0,0 +1,214 @@
-- Copyright 2020 Teverse.com
-- Tool Constants:
local toolName = "Hand"
local toolDesc = ""
local toolIcon = "fa:s-hand-pointer"
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local lightManager = require("tevgit:workshop/controllers/core/lightManager.lua")
local clickEvent = nil
local keyEvent = nil
local settingsGui = nil
local isDragging = false
return {
name = toolName,
icon = toolIcon,
desc = toolDesc,
activate = function()
settingsGui = ui.create("guiFrame", shared.workshop.interface["_toolBar"], {
size = guiCoord(0, 120, 0, 28),
position = guiCoord(0, 80, 0, 0),
backgroundAlpha = 0.8,
borderRadius = 4
}, "primary")
ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.6, 0, 0, 18),
position = guiCoord(0, 5, 0, 5),
text = "Grid Step",
fontSize = 18,
textAlpha = 0.8
}, "primaryText")
local gridStep = 0.25
local gridStepInput = ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.4, -6, 0, 18),
position = guiCoord(0.6, 3, 0, 5),
text = tostring(gridStep),
fontSize = 18,
textAlpha = 0.8,
borderRadius = 4,
align = enums.align.middle,
readOnly = false
}, "primaryVariant")
gridStepInput:on("changed", function()
gridStep = tonumber(gridStepInput.text) or 0
end)
local offsets = {}
keyEvent = engine.input:keyPressed(function( inputObj )
if inputObj.key == enums.key.r and isDragging then
local targetRot = quaternion:setEuler(0,math.rad(-45),0)
for _,v in pairs(selection.selection) do
if offsets[v] then
offsets[v][2] = offsets[v][2] * targetRot
v.rotation = offsets[v][2]
end
end
end
end)
-- Set the event listener to a variable so we can disconnect this handler
-- when the tool is deactivated
clickEvent = engine.input:mouseLeftPressed(function ( inputObj )
if not inputObj.systemHandled then
-- This is not a gui event, let's continue.
local hit = engine.physics:rayTestScreen(engine.input.mousePosition)
if hit and (not hit.object.workshopLocked or lightManager.lights[hit.object]) then
if lightManager.lights[hit.object] then
hit.object = lightManager.lights[hit.object]
end
-- user has clicked a object in 3d space.
if selection.isSelected(hit.object) then
-- user clicked a selected object,
-- we're gonna turn into drag mode!
-- calculate the 'centre' of the selection
local bounds = aabb()
if #selection.selection > 0 then
bounds.min = selection.selection[1].position
bounds.max = selection.selection[1].position
end
for _,v in pairs(selection.selection) do
local size = v.size or vector3(0,0,0)
bounds:expand(v.position + (size/2))
bounds:expand(v.position - (size/2))
end
local centre = bounds:getCentre()
-- calculate the mouse's offset from the centre
local mouseOffset = hit.hitPosition - centre
-- calculate every selected item's offset from the centre
offsets = {}
for _,v in pairs(selection.selection) do
--offsets[v] = v.position - centre
local relative = quaternion(0, 0, 0, 1) * v.rotation;
local positionOffset = (relative*quaternion(0, 0, 0, 1)):inverse() * (v.position - centre)
offsets[v] = {positionOffset, relative}
end
-- tell history to monitor changes we make to selected items
history.beginAction(selection.selection, "Hand tool drag")
local grid = engine.construct("grid", workspace, {
step = gridStep,
colour = colour(0.1, 0, 0.1),
size = 12,
rotation = quaternion:setEuler(math.rad(90), 0, 0)
})
isDragging = true
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
-- fire a ray, exclude selected items.
local hits, didExclude = engine.physics:rayTestScreenAllHits(engine.input.mousePosition, selection.selection)
if (#hits > 0) then
-- We dont want to raytest with create mode objects1
local hit = hits[1]
for _,v in pairs(hits) do
if v.object.name ~= "_CreateMode_" then
hit = v
goto skip
end
end
::skip::
local newCentre = hit.hitPosition - mouseOffset
if gridStep ~= 0 then
newCentre = shared.roundVector3(newCentre, gridStep)
end
local avgPos = vector3(0,0,0)
local minY = hit.hitPosition.y
for _,v in pairs(selection.selection) do
if offsets[v] then
v.position = newCentre + (offsets[v][2] * offsets[v][1])
-- Calculate the lowest point in the selection:
local size = v.size or vector3(0,0,0)
minY = math.min(minY, v.position.y - (size.y/2))
avgPos = avgPos + v.position
end
end
-- If the lowest object is less than the mouse's position
if minY < hit.hitPosition.y then
local offsetBy = vector3(0, hit.hitPosition.y - minY, 0)
-- increase height of each selected object so they're above the hovered position.
for _,v in pairs(selection.selection) do
if offsets[v] then
v.position = v.position + offsetBy
end
end
end
grid.position = (avgPos / #selection.selection) + vector3(0, 0.1, 0)
end
wait()
end
isDragging = false
history.endAction()
grid:destroy()
else
-- user clicked an unselected object, let's select it
if engine.input:isKeyDown(enums.key.leftShift) then
-- holding shift, so we append the clicked object to selection
selection.addSelection(hit.object)
else
-- we override the user's selection here
selection.setSelection(hit.object)
end
end
else
selection.setSelection({})
end
end
end)
end,
deactivate = function ()
if settingsGui then
settingsGui:destroy()
settingsGui = nil
end
if keyEvent then
keyEvent:disconnect()
keyEvent = nil
end
if clickEvent then
clickEvent:disconnect()
clickEvent = nil
end
end
}

View File

@ -0,0 +1,10 @@
-- Copyright 2020 Teverse.com
-- This script is responsible for loading in the other sidetools
-- This is required by the UI system and the array returned is used to generate the sidebar
return {
handTool = require("tevgit:workshop/controllers/sidetools/hand.lua"),
moveTool = require("tevgit:workshop/controllers/sidetools/move.lua"),
scaleTool = require("tevgit:workshop/controllers/sidetools/scale.lua"),
rotateTool = require("tevgit:workshop/controllers/sidetools/rotate.lua"),
}

View File

@ -0,0 +1,238 @@
-- Copyright 2020 Teverse.com
-- Tool Constants:
local toolName = "Move"
local toolDesc = ""
local toolIcon = "fa:s-arrows-alt"
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local boundingBoxChangedEvent;
local gridGuideline;
local handles;
local settingsGui = nil
local function updateHandles()
if handles then
if selection.box.size == vector3(0,0,0) then
for _,v in pairs(handles) do
v[1].size = vector3(0,0,0)
v[1].opacity = 0
v[1].line.positionA = vector3(0, 0, 0)
v[1].line.positionB = vector3(0, 0, 0)
end
else
for _,v in pairs(handles) do
v[1].position = selection.box.position + selection.box.rotation* ((v[2] * selection.box.size/2) + (v[2]*1.5))
v[1]:lookAt(selection.box.position)
v[1].rotation = v[1].rotation * quaternion():setEuler(math.rad(90), 0, 0)
v[1].size = vector3(0.2, 0.4, 0.2)
v[1].opacity = 1
v[1].line.positionA = v[1].position
v[1].line.positionB = selection.box.position
end
end
end
end
local axes = {"x", "y", "z"}
return {
name = toolName,
icon = toolIcon,
desc = toolDesc,
activate = function()
settingsGui = ui.create("guiFrame", shared.workshop.interface["_toolBar"], {
size = guiCoord(0, 120, 0, 28),
position = guiCoord(0, 80, 0, 0),
backgroundAlpha = 0.8,
borderRadius = 4
}, "primary")
ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.6, 0, 0, 18),
position = guiCoord(0, 5, 0, 5),
text = "Grid Step",
fontSize = 18,
textAlpha = 0.8
}, "primaryText")
local gridStep = 0.25
local gridStepInput = ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.4, -6, 0, 18),
position = guiCoord(0.6, 3, 0, 5),
text = tostring(gridStep),
fontSize = 18,
textAlpha = 0.8,
borderRadius = 4,
align = enums.align.middle,
readOnly = false
}, "primaryVariant")
gridStepInput:on("changed", function()
gridStep = tonumber(gridStepInput.text) or 0
end)
local offsets = {}
keyEvent = engine.input:keyPressed(function( inputObj )
if inputObj.key == enums.key.r and isDragging then
local targetRot = quaternion:setEuler(0,math.rad(-45),0)
for _,v in pairs(selection.selection) do
if offsets[v] then
offsets[v][2] = offsets[v][2] * targetRot
v.rotation = offsets[v][2]
end
end
end
end)
-- This is used to raycast the user's mouse position to an axis
gridGuideline = engine.construct("block", workspace, {
name = "_GridGuideline",
size = vector3(0, 0, 0),
colour = colour(1, 1, 1),
opacity = 0,
workshopLocked = true,
castsShadows = false
})
handles = {}
for axisNumber, axis in pairs(axes) do
for i = -1, 1, 2 do
local face = vector3(0, 0, 0)
face[axis] = i
local handle = engine.construct("block", nil, {
name = "_CreateMode_",
castsShadows = false,
opacity = 0,
renderQueue = 1,
doNotSerialise = true,
size = vector3(0.1, 0.1, 0.1),
colour = colour(axisNumber == 1 and 1 or 0, axisNumber == 2 and 1 or 0, axisNumber == 3 and 1 or 0),
--emissiveColour = colour(axisNumber == 1 and 1 or 0, axisNumber == 2 and 1 or 0, axisNumber == 3 and 1 or 0),
workshopLocked = true,
mesh = "primitive:cone"
})
engine.construct("line", handle, {name = "line", colour = handle.colour})
handle:mouseLeftPressed(function()
gridGuideline.size = vector3(300, 0.1, 300)
local gridVisual = engine.construct("grid", workspace, {
step = gridStep,
colour = colour(0.1, 0, 0.1),
size = 25,
rotation = quaternion:setEuler(math.rad(90), 0, 0)
})
local last = nil
history.beginAction(selection.selection, "Move tool drag")
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
-- Position and rotate the invisible guideline to face the camera.
-- We use this guideline to raycast with
local pos1 = gridGuideline.position
pos1[axis] = 0
local pos2 = workspace.camera.position
pos2[axis] = 0
local lookAt = gridGuideline.rotation:setLookRotation( pos1 - pos2 )
gridGuideline.rotation = lookAt * quaternion():setEuler(math.rad(90),0,0)
gridGuideline.position = selection.box.position
if axis == "y" then
gridVisual.rotation = quaternion:setLookRotation( pos1 - pos2 ) * quaternion():setEuler(math.rad(180),0,0)
end
gridVisual.position = selection.box.position
local mouseHits = engine.physics:rayTestScreenAllHits( engine.input.mousePosition )
local mouseHit = nil
-- We only want the gridGuideline
for _,hit in pairs(mouseHits) do
if hit.object == gridGuideline then
mouseHit = hit
goto skip_loop
end
end
::skip_loop::
if mouseHit and mouseHit.object == gridGuideline then
local target = mouseHit.hitPosition
local didMove = true
if last ~= nil then
local translateBy = vector3(0, 0, 0)
translateBy[axis] = target[axis] - last
if gridStep ~= 0 then
translateBy = shared.roundVector3(translateBy, gridStep)
if translateBy[axis] == 0 then
didMove = false
end
end
if didMove then
for _,v in pairs(selection.selection) do
if type(v.position) == "vector3" then
v.position = v.position + translateBy
end
end
end
end
if didMove then
last = target[axis]
end
end
wait()
end
history.endAction()
gridVisual:destroy()
gridGuideline.size = vector3(0, 0, 0)
end)
table.insert(handles, {handle, face})
end
end
boundingBoxChangedEvent = selection.box:changed(updateHandles)
updateHandles()
end,
deactivate = function ()
if settingsGui then
settingsGui:destroy()
settingsGui = nil
end
if boundingBoxChangedEvent then
boundingBoxChangedEvent:disconnect()
boundingBoxChangedEvent = nil
end
if gridGuideline then
gridGuideline:destroy()
gridGuideline = nil
end
if handles then
for _,v in pairs(handles) do
v[1]:destroy()
end
handles = nil
end
end
}

View File

@ -0,0 +1,211 @@
-- Copyright 2020 Teverse.com
-- Tool Constants:
local toolName = "Rotate"
local toolDesc = ""
local toolIcon = "fa:s-redo"
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local sensitivity = 50
local arrows = {
{},
{},
{}
}
-- Each axis gets four arrows...
-- This table maps each arrow index to an vertice index
local arrowsVerticesMap = {
{6, 4, 2, 1}, --x
{2, 1, 7, 6}, --y
{5, 7, 3, 1} --z
}
local function positionArrows()
if selection.box.size == vector3(0,0,0) then
for _,v in pairs(arrows) do
for i,arrow in pairs(v) do
arrow.physics = false
arrow.opacity = 0
end
end
else
local vertices = shared.calculateVertices(selection.box)
for a,v in pairs(arrows) do
for i,arrow in pairs(v) do
if i == 2 then i = 3 end
arrow.physics = true
arrow.opacity = 1
arrow.position = vertices[arrowsVerticesMap[a][i]]
if a == 1 then
arrow.rotation = quaternion:setEuler(math.rad((i)*90), 0, math.rad(-90))
elseif a == 2 then
arrow.rotation = quaternion:setEuler(0, math.rad((i-1)*-90), 0)
else
arrow.rotation = quaternion:setEuler(math.rad((i-1)*-90), math.rad(90), math.rad(90))
end
end
end
end
end
local boundingEvent
-- calculates angle ABC
-- returns in radians
local function calculateAngleBetween3Points(a, b, c)
local v1 = a - b
local v2 = c - b
return math.acos(v1:normal():dot(v2:normal()))
end
local function calculateCircleAngle(hitbox, pos)
local hitboxPosition = hitbox.position
local hitboxRotation = hitbox.rotation
local hitboxUp = hitboxPosition + (hitboxRotation * vector3(0,0,-10))
local hitboxRight = hitboxPosition + (hitboxRotation * vector3(10,0,0))
local angle = calculateAngleBetween3Points(hitboxUp, hitboxPosition, pos)
if hitboxRight:dot(pos) < 0 then
angle = (math.pi-angle)+math.pi
end
return angle
end
return {
name = toolName,
icon = toolIcon,
desc = toolDesc,
activate = function()
for axis = 1, 3 do
local newArrow = engine.construct("block", engine.workspace, {
name = "_CreateMode_",
castsShadows = false,
opacity = 0,
renderQueue=1,
doNotSerialise=true,
size = vector3(.4, 0.1, .4),
colour = colour(axis == 1 and 1 or 0, axis == 2 and 1 or 0, axis == 3 and 1 or 0),
--emissiveColour = colour(axis == 1 and 0.5 or 0, axis == 2 and 0.5 or 0, axis == 3 and 0.5 or 0),
workshopLocked = true,
mesh = "tevurl:3d/arrowCurved.glb"
})
newArrow:mouseLeftPressed(function ()
local hitbox = engine.construct("block", workspace, {
name = "_CreateMode_",
castsShadows = false,
opacity = 0,
renderQueue = 1,
doNotSerialise=true,
size = vector3(60, 0.1, 60),
workshopLocked = true,
position = shared.getCentreOfFace(selection.box, (axis*2)-1),
rotation = newArrow.rotation
})
hitbox.rotation = hitbox.rotation:setLookRotation( hitbox.position - workspace.camera.position ) * quaternion():setEuler(math.rad(90),0,0)
--hitbox:lookAt(workspace.camera.position)
local mouseHits = engine.physics:rayTestScreenAllHits( engine.input.mousePosition )
local mouseHit = nil
for _,hit in pairs(mouseHits) do
if hit.object == hitbox then
mouseHit = hit
goto skip_loop
end
end
::skip_loop::
if not mouseHit then
print("Did not collide")
hitbox:destroy()
return nil
end
local start = mouseHit.hitPosition
history.beginAction(selection.selection, "Rotate tool drag")
while engine.input:isMouseButtonDown(enums.mouseButton.left) and wait() do
hitbox.rotation = hitbox.rotation:setLookRotation( hitbox.position - workspace.camera.position ) * quaternion():setEuler(math.rad(90),0,0)
local mouseHits = engine.physics:rayTestScreenAllHits( engine.input.mousePosition )
local mouseHit = nil
for _,hit in pairs(mouseHits) do
if hit.object == hitbox then
mouseHit = hit
goto skip_loop
end
end
::skip_loop::
if mouseHit then
local current = mouseHit.hitPosition
local diff = (start-current)
local travelled = diff:length()
-- length of vectors is never less than 0. let's fix that
if (newArrow.rotation * vector3(0,0,1)):dot(diff) < 0 then
--user moved their mouse in an opposite direction to the arrow
travelled = -travelled
end
local n = shared.round(math.rad(travelled*sensitivity), math.rad(10))
for _,v in pairs(selection.selection) do
local euler = vector3(
axis == 1 and n or 0,
axis == 2 and n or 0,
axis == 3 and n or 0
)
v.rotation = v.rotation * quaternion:setEuler(v.rotation:inverse() * euler)
end
if n ~= 0 then
start = current
end
end
end
hitbox:destroy()
history.endAction()
end)
table.insert(arrows[axis], newArrow)
end
boundingEvent = selection.box:changed(positionArrows)
positionArrows()
end,
deactivate = function ()
boundingEvent:disconnect()
boundingEvent = nil
for _,v in pairs(arrows) do
for _,arrow in pairs(v) do
print(arrow)
arrow:destroy()
end
end
arrows = {
{},
{},
{}
}
end
}

View File

@ -0,0 +1,268 @@
-- Copyright 2020 Teverse.com
-- Tool Constants:
local toolName = "Scale"
local toolDesc = ""
local toolIcon = "fa:s-expand"
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local history = require("tevgit:workshop/controllers/core/history.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local boundingBoxChangedEvent;
local gridGuideline;
local handles;
local settingsGui = nil
local function updateHandles()
if handles then
if selection.box.size == vector3(0,0,0) then
for _,v in pairs(handles) do
v[1].size = vector3(0,0,0)
v[1].opacity = 0
v[1].line.positionA = vector3(0, 0, 0)
v[1].line.positionB = vector3(0, 0, 0)
end
else
for _,v in pairs(handles) do
v[1].position = selection.box.position + selection.box.rotation* ((v[2] * selection.box.size/2) + (v[2]*1.5))
v[1]:lookAt(selection.box.position)
v[1].rotation = v[1].rotation * quaternion():setEuler(math.rad(90), 0, 0)
v[1].size = vector3(0.4, 0.4, 0.4)
v[1].opacity = 1
v[1].line.positionA = v[1].position
v[1].line.positionB = selection.box.position
end
end
end
end
local axes = {"x", "y", "z"}
local debugBlock = engine.construct("block", nil, {
name = "_CreateMode_",
castsShadows = false,
doNotSerialise = true,
size = vector3(0.1, 0.1, 0.1),
workshopLocked = true
})
return {
name = toolName,
icon = toolIcon,
desc = toolDesc,
activate = function()
settingsGui = ui.create("guiFrame", shared.workshop.interface["_toolBar"], {
size = guiCoord(0, 120, 0, 28),
position = guiCoord(0, 80, 0, 0),
backgroundAlpha = 0.8,
borderRadius = 4
}, "primary")
ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.6, 0, 0, 18),
position = guiCoord(0, 5, 0, 5),
text = "Grid Step",
fontSize = 18,
textAlpha = 0.8
}, "primaryText")
local gridStep = 1
local gridStepInput = ui.create("guiTextBox", settingsGui, {
size = guiCoord(0.4, -6, 0, 18),
position = guiCoord(0.6, 3, 0, 5),
text = tostring(gridStep),
fontSize = 18,
textAlpha = 0.8,
borderRadius = 4,
align = enums.align.middle,
readOnly = false
}, "primaryVariant")
gridStepInput:on("changed", function()
gridStep = tonumber(gridStepInput.text) or 0
end)
local offsets = {}
keyEvent = engine.input:keyPressed(function( inputObj )
if inputObj.key == enums.key.r and isDragging then
local targetRot = quaternion:setEuler(0,math.rad(-45),0)
for _,v in pairs(selection.selection) do
if offsets[v] then
offsets[v][2] = offsets[v][2] * targetRot
v.rotation = offsets[v][2]
end
end
end
end)
-- This is used to raycast the user's mouse position to an axis
gridGuideline = engine.construct("block", workspace, {
name = "_GridGuideline",
size = vector3(0, 0, 0),
colour = colour(1, 1, 1),
opacity = 0,
workshopLocked = true,
castsShadows = false
})
handles = {}
for axisNumber, axis in pairs(axes) do
for i = -1, 1, 2 do
local face = vector3(0, 0, 0)
face[axis] = i
local handle = engine.construct("block", nil, {
name = "_CreateMode_",
castsShadows = false,
opacity = 0,
renderQueue = 1,
doNotSerialise = true,
size = vector3(0.1, 0.1, 0.1),
colour = colour(axisNumber == 1 and 1 or 0, axisNumber == 2 and 1 or 0, axisNumber == 3 and 1 or 0),
--emissiveColour = colour(axisNumber == 1 and 1 or 0, axisNumber == 2 and 1 or 0, axisNumber == 3 and 1 or 0),
workshopLocked = true
})
engine.construct("line", handle, {name = "line", colour = handle.colour})
handle:mouseLeftPressed(function()
gridGuideline.size = vector3(300, 0.1, 300)
local gridVisual = engine.construct("grid", workspace, {
step = gridStep,
colour = colour(0.1, 0, 0.1),
size = 25,
rotation = quaternion:setEuler(math.rad(90), 0, 0)
})
local last = nil
history.beginAction(selection.selection, "Scale tool drag")
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
-- Position and rotate the invisible guideline to face the camera.
-- We use this guideline to raycast with
local pos1 = gridGuideline.position
pos1[axis] = 0
local pos2 = workspace.camera.position
pos2[axis] = 0
local lookAt = gridGuideline.rotation:setLookRotation( pos1 - pos2 )
gridGuideline.rotation = lookAt * quaternion():setEuler(math.rad(90),0,0)
gridGuideline.position = selection.box.position
if axis == "y" then
gridVisual.rotation = quaternion:setLookRotation( pos1 - pos2 ) * quaternion():setEuler(math.rad(180),0,0)
end
gridVisual.position = selection.box.position
local mouseHits = engine.physics:rayTestScreenAllHits( engine.input.mousePosition )
local mouseHit = nil
-- We only want the gridGuideline
for _,hit in pairs(mouseHits) do
if hit.object == gridGuideline then
mouseHit = hit
goto skip_loop
end
end
::skip_loop::
if mouseHit and mouseHit.object == gridGuideline then
local target = mouseHit.hitPosition
local dist = selection.box.position[axis] - target[axis]
local didMove = true
if last ~= nil then
local translateBy = vector3(0, 0, 0)
translateBy[axis] = math.abs(dist) - math.abs(last)
local offsetBy = vector3(0, 0, 0)
offsetBy[axis] = dist - last
if gridStep ~= 0 then
translateBy = shared.roundVector3(translateBy, gridStep)
offsetBy = shared.roundVector3(offsetBy, gridStep)
if translateBy[axis] == 0 then
didMove = false
end
end
print(translateBy, offsetBy)
if didMove then
--print(dist, translateBy)
for _,v in pairs(selection.selection) do
if type(v.position) == "vector3" and type(v.size) == "vector3" then
local scaleBy = (v.rotation * translateBy)
scaleBy = vector3(math.abs(scaleBy.x), math.abs(scaleBy.y), math.abs(scaleBy.z))
if scaleBy.x < 0.01 then
scaleBy.x = 0
end
if scaleBy.y < 0.01 then
scaleBy.y = 0
end
if scaleBy.z < 0.01 then
scaleBy.z = 0
end
if translateBy[axis] < 0 then
scaleBy = -scaleBy
end
v.position = v.position - (offsetBy / 2)
v.size = v.size + scaleBy
end
end
end
end
if didMove then
last = dist
end
end
wait()
end
history.endAction()
gridVisual:destroy()
gridGuideline.size = vector3(0, 0, 0)
end)
table.insert(handles, {handle, face})
end
end
boundingBoxChangedEvent = selection.box:changed(updateHandles)
updateHandles()
end,
deactivate = function ()
if settingsGui then
settingsGui:destroy()
settingsGui = nil
end
if boundingBoxChangedEvent then
boundingBoxChangedEvent:disconnect()
boundingBoxChangedEvent = nil
end
if gridGuideline then
gridGuideline:destroy()
gridGuideline = nil
end
if handles then
for _,v in pairs(handles) do
v[1]:destroy()
end
handles = nil
end
end
}

View File

@ -0,0 +1,19 @@
-- Auto Save
local shared = require("tevgit:workshop/controllers/shared.lua")
local autoSave = {}
autoSave.Enabled = false
autoSave.timeLimit = 5 -- Default (move to settings value soon)
autoSave.Sync = function()
return spawnThread(function()
while true do
if autoSave.Enabled then
shared.workshop:saveGame()
end
wait(autoSave.timeLimit)
end
end)
end
return autoSave

View File

@ -0,0 +1,299 @@
--[[
Requires refactoring
]]
local controller = {}
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local hues = {
colour(1,0,0),
colour(1,0,1),
colour(0,0,1),
colour(0,1,1),
colour(0,1,0),
colour(1,1,0),
colour(1,0,0),
}
local window = ui.window(shared.workshop.interface,
"Colour Picker",
guiCoord(0, 300, 0, 186),
guiCoord(0.5, -150, 0.5, -93),
false,
true)
window.zIndex = 10
window.visible = false
local callback = nil
local startColour = colour(1,0,0)
local currentColour = colour(1,0,0)
local colourPickerGradient = engine.construct("guiFrameMultiColour", window.content, {
name = "square",
size = guiCoord(0, 150, 0, 150),
position = guiCoord(0, 5, 0, 5),
topLeftColour = colour(1,1,1),
topRightColour = startColour,
bottomLeftColour = colour(1,1,1),
bottomRightColour = startColour
})
-- To have black on the bottom we need to overlay this...
engine.construct("guiFrameMultiColour", colourPickerGradient, {
name = "overlay",
size = guiCoord(1,0,1,0),
position = guiCoord(0, 0, 0, 0),
topLeftColour = colour(0,0,0),
topLeftAlpha = 0,
topRightColour = colour(0,0,0),
topRightAlpha = 0,
bottomLeftColour = colour(0,0,0),
bottomLeftAlpha = 1,
bottomRightColour = colour(0,0,0),
bottomRightAlpha = 1,
handleEvents = false
})
local marker = engine.construct("guiFrame", colourPickerGradient, {
name = "marker",
size = guiCoord(0, 6, 0, 6),
position = guiCoord(0, 0, 0, 0),
handleEvents=false,
backgroundColour = colour(1,1,1),
borderColour = colour(0,0,0),
zIndex = 10,
borderWidth = 1,
borderRadius = 6,
borderAlpha = 1
})
local hueBar = engine.construct("guiFrame", window.content, {
name = "hueBar",
size = guiCoord(0, 30, 1, -10),
position = guiCoord(0, 160, 0, 5),
backgroundAlpha = 0
})
local hueBarMARKER = engine.construct("guiFrame", hueBar, {
name = "hueBarMARKER",
size = guiCoord(1, 0, 0, 1),
position = guiCoord(0, 0, 0, 0),
handleEvents=false,
backgroundAlpha = 0,
borderColour = colour(0,0,0),
zIndex = 10,
borderWidth = 2,
borderAlpha = 1
})
for i = 1, 6 do
local colourPickerGradient = engine.construct("guiFrameMultiColour", hueBar, {
handleEvents = false,
size = guiCoord(1, 0, 1/6, 1),
position = guiCoord(0, 0, (i-1)*(1/6), 0),
topLeftColour = hues[i],
topRightColour = hues[i],
bottomLeftColour = hues[i+1],
bottomRightColour = hues[i+1],
handleEvents = false
})
end
local rLabel = ui.create("guiTextBox", window.content, {
name = "labelR",
size = guiCoord(0, 20, 0, 16),
position = guiCoord(0,200,0,5),
fontSize = 16,
textAlpha = 0.6,
text = "R",
align = enums.align.topLeft
}, "primaryText")
local rInput = ui.create("guiTextBox", window.content, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "r",
size = guiCoord(1, -220, 0,16),
position = guiCoord(0, 220, 0, 5),
text = "1",
align = enums.align.middle
}, "primary")
local gLabel = rLabel:clone()
gLabel.name = "gLabel"
gLabel.text = "G"
gLabel.parent = window.content
gLabel.position = guiCoord(0, 200, 0, 22)
--themeController.add(gLabel, "primaryText")
local g = rInput:clone()
g.name = "g"
g.parent = window.content
g.position = guiCoord(0, 220, 0, 22)
-- themeController.add(g, "primary")
local bLabel = rLabel:clone()
bLabel.name = "bLabel"
bLabel.text = "B"
bLabel.parent = window.content
bLabel.position = guiCoord(0, 200, 0, 39)
--themeController.add(bLabel, "primaryText")
local b = rInput:clone()
b.name = "b"
b.parent = window.content
b.position = guiCoord(0, 220, 0, 39)
local hexLabel = rLabel:clone()
hexLabel.name = "hexLabel"
hexLabel.text = "#"
hexLabel.parent = window.content
hexLabel.position = guiCoord(0, 200, 0, 56)
--themeController.add(bLabel, "primaryText")
local HEX = rInput:clone()
HEX.name = "FFFFFF"
HEX.parent = window.content
HEX.position = guiCoord(0, 220, 0, 56)
-- themeController.add(b, "primary")
local preview = engine.construct("guiFrame", window.content, {
position = guiCoord(0, 220, 0, 73),
size = guiCoord(1, -220, 0, 16)
})
local function handler()
local newR = tonumber(rInput.text)
local newG = tonumber(g.text)
local newB = tonumber(b.text)
if not newR or not newG or not newB then return end
controller.setColour(colour:fromRGB(newR, newG, newB))
end
rInput:textInput(handler)
g:textInput(handler)
b:textInput(handler)
HEX:textInput(function()
controller.setColour(colour:fromHex(HEX.text), true)
end)
hueBar:mouseLeftPressed(function ()
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
local pos = engine.input.mousePosition - hueBar.absolutePosition
local size = hueBar.absoluteSize
local y = pos.y/hueBar.absoluteSize.y
local sector = math.ceil(pos.y/(size.y * (1/6)))
local hue = hues[sector]
if hue and hues[sector+1] then
hueBarMARKER.position = guiCoord(0,0,y,0)
local selected = hue:lerp(hues[sector+1], (y - ((size.y * ((sector-1)/6))/hueBar.absoluteSize.y)) / (1/6))
startColour = selected
colourPickerGradient.topRightColour = startColour
colourPickerGradient.bottomRightColour = startColour
local x = (marker.position.offsetX)/colourPickerGradient.absoluteSize.x
local y = (marker.position.offsetY)/colourPickerGradient.absoluteSize.y
local selectedColour = startColour:lerp(colour(1,1,1), 1-x)
selectedColour = selectedColour:lerp(colour(0,0,0), y)
preview.backgroundColour = selectedColour
rInput.text = tostring(math.floor(selectedColour.r*255))
g.text = tostring(math.floor(selectedColour.g*255))
b.text = tostring(math.floor(selectedColour.b*255))
HEX.text = selectedColour:getHex()
end
wait()
end
if callback then
callback(preview.backgroundColour)
end
end)
colourPickerGradient:mouseLeftPressed(function ()
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
local pos = engine.input.mousePosition - colourPickerGradient.absolutePosition
marker.position = guiCoord(0, pos.x-2, 0, pos.y-2)
local x = pos.x/colourPickerGradient.absoluteSize.x
local y = pos.y/colourPickerGradient.absoluteSize.y
local selectedColour = startColour:lerp(colour(1,1,1), 1-x)
selectedColour = selectedColour:lerp(colour(0,0,0), y)
preview.backgroundColour = selectedColour
rInput.text = tostring(math.floor(selectedColour.r*255))
g.text = tostring(math.floor(selectedColour.g*255))
b.text = tostring(math.floor(selectedColour.b*255))
HEX.text = selectedColour:getHex()
wait()
end
if callback then
callback(preview.backgroundColour)
end
end)
controller.setColour = function(c, dontUpdateHex)
local h,s,l = c:getHSV()
h=(1-h)*360
local markerh = math.ceil(h / 60)
if markerh <= 0 then markerh = 1 end
local pos = hueBar.absolutePosition
local size = hueBar.absoluteSize
local hue = hues[markerh]
local selected = hue:lerp(hues[markerh+1], ((h - (60*(markerh-1)))/60))
startColour = selected
colourPickerGradient.topRightColour = startColour
colourPickerGradient.bottomRightColour = startColour
preview.backgroundColour = c
rInput.text = tostring(math.floor(c.r*255))
g.text = tostring(math.floor(c.g*255))
b.text = tostring(math.floor(c.b*255))
if not dontUpdateHex then
HEX.text = c:getHex()
end
marker.position = guiCoord(0, (s) * colourPickerGradient.absoluteSize.x, 0, (1-l) * colourPickerGradient.absoluteSize.y)
marker.position = marker.position + guiCoord(0, -2, 0, -2)
hueBarMARKER.position = guiCoord(0,0,h/360,0)
if callback then
callback(preview.backgroundColour)
end
end
controller.window = window
controller.prompt = function(startColour, cb)
callback = nil
controller.setColour(startColour)
callback = cb
controller.window.visible = true
end
return controller

View File

@ -0,0 +1,220 @@
local controller = {}
local shared = require("tevgit:workshop/controllers/shared.lua")
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local context = require("tevgit:workshop/controllers/ui/core/contextMenu.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
-- store icons for each class type
-- use a table of two icons,
-- [1] will be unexpanded and [2] will be used when the obj is expanded.
local overridingIcons = {
script = "fa:s-file-code",
input = {"fa:s-keyboard", "fa:r-keyboard"},
debug = "fa:s-bug",
light = "fa:s-lightbulb",
block = "fa:s-cube",
camera = "fa:s-camera",
}
-- dictionary of buttons to their corrosponding objects.
local buttonToObject = {}
local function updatePositions(frame)
local y = 10
if not frame then
frame = controller.scrollView
else
y = 20
end
if frame.children then
for _, v in pairs(frame.children) do
if v.name ~= "icon" then
v.position = guiCoord(0, 10, 0, y)
y = y + updatePositions(v)
end
end
end
if type(frame) == "guiTextBox" then
local regularIconWithChildren = "fa:s-folder"
local regularIconWithOutChildren = "fa:r-folder"
local expandedIcon = "fa:s-folder-open"
local icons = overridingIcons[buttonToObject[frame].className]
if icons then
if type(icons) == "string" then
regularIconWithChildren = icons
regularIconWithOutChildren = icons
else
regularIconWithChildren = icons[1]
regularIconWithOutChildren = icons[1]
expandedIcon = icons[2]
end
end
if y == 20 then
-- no children
if buttonToObject[frame] and buttonToObject[frame].children and
#buttonToObject[frame].children > 0 then
-- object has children but is not expanded
frame.icon.texture = regularIconWithChildren
frame.icon.imageAlpha = 1
frame.textAlpha = 1
frame.fontFile = "local:OpenSans-SemiBold.ttf"
else
-- object has no children
frame.icon.texture = regularIconWithOutChildren
frame.icon.imageAlpha = 0.5
frame.textAlpha = .6
frame.fontFile = "local:OpenSans-Regular.ttf"
end
else
-- object is expanded
frame.textAlpha = 0.6
frame.fontFile = "local:OpenSans-Regular.ttf"
frame.icon.imageAlpha = 0.75
frame.icon.texture = expandedIcon
end
if buttonToObject[frame] and selection.isSelected(buttonToObject[frame]) then
frame.backgroundAlpha = 0.3
else
frame.backgroundAlpha = 0
end
end
return y
end
controller.updatePositions = updatePositions
local function createHierarchyButton(object, guiParent)
local btn = ui.create("guiTextBox", guiParent, {
text = " " .. object.name, -- ik...
size = guiCoord(1, -6, 0, 18),
fontSize = 16,
cropChildren = false,
backgroundAlpha = 0,
hoverCursor = "fa:s-hand-pointer"
}, "backgroundText")
buttonToObject[btn] = object
local icon = ui.create("guiImage", btn, {
name = "icon",
texture = "fa:s-folder",
position = guiCoord(0, 1, 0, 1),
size = guiCoord(0, 16, 0, 16),
handleEvents = false,
backgroundAlpha = 0
})
local expanded = false
local lastClick = 0
btn:onSync("mouseRightPressed", function()
if (object:isA("folder")) then
selection.setSelection(object.children)
propertyEditor.generateProperties(object)
else
selection.setSelection({object})
end
controller.scrollView.canvasSize = guiCoord(1, 0, 0, updatePositions())
end)
btn:mouseLeftReleased(function()
if os.time() - lastClick < 0.35 then
lastClick = 0
-- expand
expanded = not expanded
if expanded then
for _, child in pairs(object.children) do
if child.name ~= "_CreateMode_" then
createHierarchyButton(child, btn)
end
end
controller.scrollView.canvasSize =
guiCoord(1, 0, 0, updatePositions())
if object.className == "script" then
object:editExternal()
-- require("tevgit:create/controllers/scriptController.lua").editScript(object)
end
else
for _, v in pairs(btn.children) do
if v.name ~= "icon" then
if buttonToObject[v] then
buttonToObject[v] = nil
end
v:destroy()
end
end
controller.scrollView.canvasSize =
guiCoord(1, 0, 0, updatePositions())
end
elseif object.name ~= "_bounding" then
-- single click
local currentTime = os.time()
lastClick = currentTime
if (object:isA("folder")) then
selection.setSelection(object.children)
propertyEditor.generateProperties(object)
else
selection.setSelection(object)
end
controller.scrollView.canvasSize =
guiCoord(1, 0, 0, updatePositions())
end
end)
local childAddedEvent = object:on("childAdded", function(child)
if expanded then createHierarchyButton(child, btn) end
controller.scrollView.canvasSize = guiCoord(1, 0, 0, updatePositions())
end)
local childRemovedEvent = object:onSync("childRemoved", function(child)
if expanded then
for button, obj in pairs(buttonToObject) do
if obj == child and button.alive then
button:destroy()
end
end
end
controller.scrollView.canvasSize = guiCoord(1, 0, 0, updatePositions())
end)
btn:once("destroying", function() childAddedEvent:disconnect() end)
if object:isA("luaSharedFolder")
or object:isA("luaServerFolder")
or object:isA("luaClientFolder") then
context.bind(btn, {
{name = "Add Script", callback = function() engine.construct("script", object) end}
})
else
-- selectionController.applyContext(btn)
end
return btn
end
controller.window = ui.window(shared.workshop.interface, "Hierarchy",
guiCoord(0, 260, 0, 400), -- size
guiCoord(1, -260, 0.75, -25), -- pos
true, -- dockable
true -- hidable
)
controller.window.visible = true
controller.scrollView = ui.create("guiScrollView", controller.window.content, {
name = "scrollview",
size = guiCoord(1, 0, 1, 0)
}, "primaryText")
createHierarchyButton(engine, controller.scrollView)
controller.scrollView.canvasSize = guiCoord(1, 0, 0, updatePositions())
return controller

View File

@ -0,0 +1,64 @@
-- the front-end for /workshop/controllers/core/history.lua
local shared = require("tevgit:workshop/controllers/shared.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local history = shared.controllers.history
local window = ui.window(shared.workshop.interface,
"History",
guiCoord(0, 420, 0, 250), --size
guiCoord(0.5, -210, 0.5, -25), --pos
false, --dockable
true -- hidable
)
local function draw()
window.content:destroyAllChildren()
local actions = history.getActions()
local latestAction = history.getPointer()
local yPos = 0
-- render 'future' actions that are still on the stack
-- these only exist if the user has undone something but hasn't started a new action
if actions[latestAction+1] ~= nil then
local i = latestAction + 1
while actions[i] ~= nil do
local action = actions[i]
local formattedDate = os.date("%H:%M:%S", action[1])
local actionName = action[2]:len() > 5 and action[2]:sub(0, 4) .. "..." or action[2]
ui.create("guiTextBox", window.content, {
size = guiCoord(1, 0, 0, 18),
position = guiCoord(0, 0, 0, yPos),
fontFile = "tevurl:font/OpenSans-Italic.ttf",
text = string.format("[ UNDONE ] %s (change: %i, add: %i, rem: %i)", actionName, history.count(action[3]), history.count(action[4]), history.count(action[5]))
}, "backgroundText")
yPos = yPos + 20
i = i + 1
end
end
for i = latestAction, 0, -1 do
local action = actions[i]
if action then
local formattedDate = os.date("%H:%M:%S", action[1])
local actionName = action[2]:len() > 5 and action[2]:sub(0, 4) .. "..." or action[2]
ui.create("guiTextBox", window.content, {
size = guiCoord(1, 0, 0, 18),
position = guiCoord(0, 0, 0, yPos),
text = string.format("[ %s ] %s (change: %i, add: %i, rem: %i)", formattedDate, actionName, history.count(action[3]), #action[4], #action[5])
}, "backgroundText")
yPos = yPos + 20
end
end
end
draw()
history.setCallback(draw)
return window

View File

@ -0,0 +1,127 @@
local shared = require("tevgit:workshop/controllers/shared.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local keybinder = require("tevgit:workshop/controllers/core/keybinder.lua")
local history = shared.controllers.history
local window = ui.create("guiFrame", shared.workshop.interface["_toolBar"], {
size = guiCoord(0, 32, 0, 100),
position = guiCoord(0, 40, 0, 0),
backgroundAlpha = 0.8,
borderRadius = 4
}, "primary")
local function insert(mesh)
if not mesh then
mesh = "primitive:cube"
end
local insertPos = vector3(0, 0, 0)
local hit = engine.physics:rayTestScreen(engine.input.screenSize/2) -- what's in the centre of the screen?
if hit then
local hitDist = (shared.controllers.env.camera.camera.position - hit.hitPosition):length()
if hitDist < 1 or hitDist > 40 then
insertPos = shared.controllers.env.camera.camera.position + (shared.controllers.env.camera.camera.rotation * vector3(0, 0, 10))
else
insertPos = hit.hitPosition + vector3(0, 0.5, 0)
end
else
insertPos = shared.controllers.env.camera.camera.position + (shared.controllers.env.camera.camera.rotation * vector3(0, 0, 10))
end
history.beginAction(workspace, "Inserter")
local block;
if mesh ~= "light" then
block = engine.construct("block", workspace, {
mesh = mesh,
colour = colour(0.8, 0.8, 0.8),
position = insertPos
})
else
block = engine.construct("light", workspace, {
position = insertPos
})
end
history.endAction()
return block
end
local adders = {
{
name = "Cube",
icon = "fa:s-cube",
callback = function()
insert()
end
},
{
name = "Sphere",
icon = "fa:s-globe",
callback = function()
insert("primitive:sphere")
end
},
{
name = "Light",
icon = "fa:s-lightbulb",
callback = function()
insert("light")
end
}
}
local currentY = 5
local toolIndex = 0
for _, options in pairs(adders) do
-- used to assign a number keybind to each tool
toolIndex = toolIndex + 1
-- options is the table returned by the tool's module.
-- e.g. workshop/controllers/sidetools/hand.lua
local newTabBtn = ui.create("guiTextBox", window, {
text = options.name,
position = guiCoord(0, 4, 0, currentY),
size = guiCoord(0, 24, 0, 24),
hoverCursor = "fa:s-hand-pointer"
}, "primaryText")
if options.icon then
newTabBtn.text = ""
ui.create("guiImage", newTabBtn, {
name = "icon",
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0., 0),
texture = options.icon,
handleEvents = false,
imageAlpha = 0.75
}, "primaryImage")
end
local keybindText = ""
if toolIndex < 10 then
keybindText = " [ALT + " .. toolIndex .. "]"
keybinder:bind({
name = "Insert " .. options.name .. " shape",
priorKey = enums.key.alt,
key = enums.key["number"..toolIndex],
action = options.callback
})
end
-- shows a tool tip if the user hovers over the button
ui.tooltip(newTabBtn, options.name .. keybindText)
newTabBtn:mouseLeftPressed(options.callback)
currentY = currentY + 32
end
window.size = guiCoord(0, 32, 0, currentY - 5)
return window

View File

@ -0,0 +1,643 @@
-- this module is responsible for creating the inputguis
local modulePrefix = "tevgit:workshop/controllers/ui/components/propertyEditor/"
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
local colourPicker = require("tevgit:workshop/controllers/ui/components/colourPicker.lua")
local parseInputs = require(modulePrefix .. "parseInputs.lua")
local meshShortcuts = require(modulePrefix .. "meshShortcuts.lua")
local createInputs;
createInputs = {
default = function(instance, property, value)
return ui.create("guiFrame", nil, {
backgroundAlpha = 0.25,
name = "inputContainer",
size = guiCoord(0.45, 0, 0, 20),
position = guiCoord(0.55,0,0,0),
cropChildren = false
}, "secondary")
end,
block = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = true,
fontSize = 18,
name = "input",
size = guiCoord(1, -4, 1, -2),
position = guiCoord(0, 2, 0, 1),
text = "Instance Selector",
align = enums.align.middle
}, "primary")
return container
end,
boolean = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
container.backgroundAlpha = 0
local x = ui.create("guiImage", container, {
name = "input",
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0, 0, 0, 0),
texture = "fa:s-toggle-on"
}, "successImage")
x:mouseLeftReleased(function ()
value = not value
x.texture = value and "fa:s-toggle-on" or "fa:s-toggle-off"
themer.registerGui(x, value and "successImage" or "errorImage")
parseInputs[type(value)](property, container)
end)
return container
end,
number = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "input",
size = guiCoord(1, -4, 0, 18),
position = guiCoord(0, 2, 0, 1),
text = "0",
align = enums.align.middle
}, "primary")
x:textInput(function ()
parseInputs[type(value)](property, container)
end)
if property == "type" and type(instance) == "light" then
container.zIndex = 30 -- important because child elements need to be rendered above other properties!
container.size = container.size + guiCoord(0,0,0,20)
local presetSelect = ui.create("guiTextBox", container, {
size = guiCoord(1, -4, 0, 16),
position = guiCoord(0, 2, 0, 23),
borderRadius = 3,
text = "Light Options",
fontSize = 16,
align = enums.align.middle,
backgroundAlpha = 0.75
}, "primary")
local optionsModal = ui.create("guiFrame", container, {
position = guiCoord(-0.8, 7, 0, 48),
borderRadius = 6,
visible = false,
zIndex = 40,
borderWidth = 1,
cropChildren = false
}, "main")
local isFocused = false
local pendingHide = false
local function queueCloseModal()
if not pendingHide and optionsModal.visible then
pendingHide = true
wait(.4)
if not isFocused then
--still unfocused, lets hide.
optionsModal.visible = false
end
pendingHide=false
end
end
presetSelect:mouseFocused(function ()
optionsModal.visible = true
isFocused = true
end)
optionsModal:mouseFocused(function ()
isFocused = true
end)
presetSelect:mouseUnfocused(function ()
isFocused = false
queueCloseModal()
end)
optionsModal:mouseUnfocused(function ()
isFocused = false
queueCloseModal()
end)
ui.create("guiImage", optionsModal, {
size = guiCoord(0, 24, 0, 24),
position = guiCoord(0.75, -12, 0, -15),
handleEvents=false,
zIndex = 10,
backgroundAlpha = 0,
texture = "fa:s-caret-up",
imageColour = optionsModal.backgroundColour
})
local curY = 0
local curX = 0
for lightType, num in pairs(enums.lightType) do
local btn = ui.create("guiTextBox", optionsModal, {
size = guiCoord(.5, -10, 0, 18),
position = guiCoord(curX, 5, 0, curY + 4),
borderRadius = 3,
text = lightType,
fontSize = 16,
align = enums.align.middle
}, "primary")
btn:mouseFocused(function ()
isFocused = true
end)
btn:mouseUnfocused(function ()
isFocused = false
queueCloseModal()
end)
btn:mouseLeftReleased(function ()
x.text = tostring(num)
parseInputs[type(value)](property, container)
end)
if curX == 0.5 then
curY = curY + 24
curX = 0
else
curX = 0.5
end
end
if curX == 0.5 then
curY = curY + 24
end
optionsModal.size = guiCoord(1.8, -10, 0, curY+4)
end
return container
end,
string = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
wrap = true,
fontSize = 18,
name = "input",
size = guiCoord(1, -4, 0, 18),
position = guiCoord(0, 2, 0, 1),
text = "text input",
align = enums.align.middleLeft,
zIndex = 2,
cropChildren = true
}, "primary")
x:textInput(function ()
parseInputs[type(value)](property, container)
end)
-- TODO TODO TODO TODO
-- We need some sort of helper function that'll make
-- modals for situations like this:
if property == "mesh" then
container.zIndex = 30 -- important because child elements need to be rendered above other properties!
container.size = container.size + guiCoord(0,0,0,20)
x.fontSize = 14
local presetSelect = ui.create("guiTextBox", container, {
size = guiCoord(1, -4, 0, 16),
position = guiCoord(0, 2, 0, 23),
borderRadius = 3,
text = "Mesh Presets",
fontSize = 16,
align = enums.align.middle,
backgroundAlpha = 0.75
}, "primary")
local meshModal = ui.create("guiFrame", container, {
position = guiCoord(-0.8, 7, 0, 48),
borderRadius = 6,
visible = false,
zIndex = 40,
borderWidth = 1,
cropChildren = false
}, "main")
local isFocused = false
local pendingHide = false
local function queueCloseModal()
if not pendingHide and meshModal.visible then
pendingHide = true
spawnThread(function ()
-- Code here...
wait(.4)
if not isFocused then
--still unfocused, lets hide.
meshModal.visible = false
end
pendingHide=false
end)
end
end
presetSelect:onSync("mouseFocused", function ()
meshModal.visible = true
isFocused = true
end)
meshModal:onSync("mouseFocused", function ()
isFocused = true
end)
presetSelect:onSync("mouseUnfocused", function ()
isFocused = false
queueCloseModal()
end)
meshModal:onSync("mouseUnfocused", function ()
isFocused = false
queueCloseModal()
end)
ui.create("guiImage", meshModal, {
size = guiCoord(0, 24, 0, 24),
position = guiCoord(0.75, -12, 0, -15),
handleEvents=false,
zIndex = 10,
backgroundAlpha = 0,
texture = "fa:s-caret-up",
imageColour = meshModal.backgroundColour
})
local curY = 0
local curX = 0
for meshName, actualMeshName in pairs(meshShortcuts) do
local btn = ui.create("guiTextBox", meshModal, {
size = guiCoord(.5, -10, 0, 18),
position = guiCoord(curX, 5, 0, curY + 4),
borderRadius = 3,
text = meshName,
fontSize = 16,
align = enums.align.middle
}, "primary")
btn:onSync("mouseFocused", function ()
isFocused = true
end)
btn:onSync("mouseUnfocused", function ()
isFocused = false
queueCloseModal()
end)
btn:mouseLeftReleased(function ()
x.text = actualMeshName
parseInputs[type(value)](property, container)
end)
if curX == 0.5 then
curY = curY + 24
curX = 0
else
curX = 0.5
end
end
if curX == 0.5 then
curY = curY + 24
end
meshModal.size = guiCoord(1.8, -10, 0, curY+4)
end
return container
end,
vector3 = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
container.size = guiCoord(container.size.scaleX, 0, 0, 60)
local xLabel = ui.create("guiTextBox", container, {
name = "labelX",
size = guiCoord(0, 10, 1/3, -1),
position = guiCoord(0,-10,0,1),
fontSize = 16,
textAlpha = 0.6,
text = "X",
align = enums.align.topLeft
}, "backgroundText")
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "x",
size = guiCoord(1, -4, 1/3, -1),
position = guiCoord(0, 2, 0, 0),
text = "0",
align = enums.align.middle
}, "primary")
local yLabel = xLabel:clone()
yLabel.name = "yLabel"
yLabel.text = "Y"
yLabel.parent = container
yLabel.position = guiCoord(0, -10, 1/3, 1)
themer.registerGui(yLabel, "backgroundText")
local y = x:clone()
y.name = "y"
y.parent = container
y.position = guiCoord(0, 2, 1/3, 0)
themer.registerGui(y, "primary")
local zLabel = xLabel:clone()
zLabel.name = "zLabel"
zLabel.text = "Z"
zLabel.parent = container
zLabel.position = guiCoord(0, -10, 2/3, 1)
themer.registerGui(yLabel, "backgroundText")
local z = x:clone()
z.name = "z"
z.parent = container
z.position = guiCoord(0, 2, 2/3, 0)
themer.registerGui(z, "primary")
local function handler()
parseInputs[type(value)](property, container)
end
x:textInput(handler)
y:textInput(handler)
z:textInput(handler)
return container
end,
vector2 = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
container.size = guiCoord(container.size.scaleX, 0, 0, 40)
local xLabel = ui.create("guiTextBox", container, {
name = "labelX",
size = guiCoord(0, 10, 1/2, -1),
position = guiCoord(0,-10,0,2),
fontSize = 16,
textAlpha = 0.6,
text = "X",
align = enums.align.topLeft
}, "backgroundText")
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "x",
size = guiCoord(0, -4, 1/2, -2),
position = guiCoord(0, 2, 0, 1),
text = "0",
align = enums.align.middle
}, "primary")
local yLabel = xLabel:clone()
yLabel.name = "yLabel"
yLabel.text = "Y"
yLabel.parent = container
yLabel.position = guiCoord(0, -10, 1/2, 2)
themer.registerGui(yLabel, "backgroundText")
local y = x:clone()
y.name = "y"
y.parent = container
y.position = guiCoord(0, 2, 1/2, 1)
themer.registerGui(y, "primary")
local function handler()
parseInputs[type(value)](property, container)
end
x:textInput(handler)
y:textInput(handler)
return container
end,
quaternion = function(instance, property, value)
-- maybe quaternions need an Euler editor?
local container = createInputs.default(value, pType, readOnly)
container.size = guiCoord(container.size.scaleX, 0, 0, 60)
local xLabel = ui.create("guiTextBox", container, {
name = "labelX",
size = guiCoord(0, 12, 1/3, -1),
position = guiCoord(0,-10,0,1),
fontSize = 16,
textAlpha = 0.6,
text = "X",
align = enums.align.topLeft
}, "backgroundText")
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "x",
size = guiCoord(1, -4, 1/3, -1),
position = guiCoord(0, 2, 0, 0),
text = "0",
align = enums.align.middle
}, "primary")
local yLabel = xLabel:clone()
yLabel.name = "yLabel"
yLabel.text = "Y"
yLabel.parent = container
yLabel.position = guiCoord(0, -10, 1/3, 1)
themer.registerGui(yLabel, "backgroundText")
local y = x:clone()
y.name = "y"
y.parent = container
y.position = guiCoord(0, 2, 1/3, 0)
themer.registerGui(y, "primary")
local zLabel = xLabel:clone()
zLabel.name = "zLabel"
zLabel.text = "Z"
zLabel.parent = container
zLabel.position = guiCoord(0, -10, 2/3, 1)
themer.registerGui(zLabel, "backgroundText")
local z = x:clone()
z.name = "z"
z.parent = container
z.position = guiCoord(0, 2, 2/3, 0)
themer.registerGui(z, "primary")
--[[local wLabel = xLabel:clone()
wLabel.name = "wLabel"
wLabel.text = "W"
wLabel.parent = container
wLabel.position = guiCoord(0, -12, 3/4, 2)
themer.registerGui(wLabel, "backgroundText")
local w = x:clone()
w.name = "w"
w.parent = container
w.position = guiCoord(0, 2, 3/4, 1)
themer.registerGui(w, "primary")]]
local function handler()
parseInputs[type(value)](property, container)
end
x:textInput(handler)
y:textInput(handler)
z:textInput(handler)
--w:textInput(handler)
return container
end,
guiCoord = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "scaleX",
size = guiCoord(1/4, -4, 1, -2),
position = guiCoord(0, 2, 0, 1),
text = "0",
align = enums.align.middle
}, "primary")
local y = x:clone()
y.name = "offsetX"
y.parent = container
y.position = guiCoord(1/4, 2, 0, 1)
themer.registerGui(y, "primary")
local z = x:clone()
z.name = "scaleY"
z.parent = container
z.position = guiCoord(1/2, 2, 0, 1)
themer.registerGui(z, "primary")
local w = x:clone()
w.name = "offsetY"
w.parent = container
w.position = guiCoord(3/4, 2, 0, 1)
themer.registerGui(w, "primary")
local function handler()
parseInputs[type(value)](property, container)
end
x:textInput(handler)
y:textInput(handler)
z:textInput(handler)
w:textInput(handler)
return container
end,
colour = function(instance, property, value)
local container = createInputs.default(value, pType, readOnly)
container.size = guiCoord(container.size.scaleX, 0, 0, 60)
local rLabel = ui.create("guiTextBox", container, {
name = "labelR",
size = guiCoord(0, 10, 1/3, -1),
position = guiCoord(0,-10,0,2),
fontSize = 16,
textAlpha = 0.6,
text = "R",
align = enums.align.topLeft
}, "backgroundText")
local x = ui.create("guiTextBox", container, {
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
fontSize = 18,
name = "r",
size = guiCoord(1, -24, 1/3, -2),
position = guiCoord(0, 2, 0, 1),
text = "1",
align = enums.align.middle
}, "primary")
local gLabel = rLabel:clone()
gLabel.name = "gLabel"
gLabel.text = "G"
gLabel.parent = container
gLabel.position = guiCoord(0, -10, 1/3, 1)
themer.registerGui(gLabel, "backgroundText")
local g = x:clone()
g.name = "g"
g.parent = container
g.position = guiCoord(0, 2, 1/3, 1)
themer.registerGui(g, "primary")
local bLabel = rLabel:clone()
bLabel.name = "bLabel"
bLabel.text = "B"
bLabel.parent = container
bLabel.position = guiCoord(0, -10, 2/3, 1)
themer.registerGui(bLabel, "backgroundText")
local b = x:clone()
b.name = "b"
b.parent = container
b.position = guiCoord(0, 2, 2/3, 1)
themer.registerGui(b, "primary")
local function handler()
parseInputs[type(value)](property, container)
end
x:textInput(handler)
g:textInput(handler)
b:textInput(handler)
local col = engine.construct("guiFrame", container, {
name = "col",
size = guiCoord(0, 14, 1, -2),
position = guiCoord(1, -18, 0, 1),
backgroundColour = colour(1,1,1),
borderRadius = 2,
})
col:mouseLeftReleased(function ()
colourPicker.prompt(value, function(c)
instance[property] = c
end)
end)
return container
end,
}
return createInputs

View File

@ -0,0 +1,16 @@
-- List of mesh shortcuts for the mesh data member of objects
local meshShortcuts = {
cube = "primitive:cube",
sphere = "primitive:sphere",
cylinder = "primitive:cylinder",
torus = "primitive:torus",
cone = "primitive:cone",
wedge = "primitive:wedge",
corner = "primitive:corner",
worker = "tevurl:3d/worker.glb",
duck = "tevurl:3d/Duck.glb",
avocado = "tevurl:3d/Avocado.glb",
}
return meshShortcuts

View File

@ -0,0 +1,73 @@
-- This module is responsible for converting the guis values to the appropriate datatype,
-- AND updating the selections properties
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local function callbackInput(property, value)
local success, message = pcall(function ()
for _,v in pairs(selection.selection) do
if v[property] ~= nil then
v[property] = value
end
end
end)
if not success then print(message) end
end
return {
block = function (property, gui)
end,
boolean = function (property, gui)
callbackInput(property, gui.input.texture == "fa:s-toggle-on")
end,
number = function (property, gui)
local num = tonumber(gui.input.text)
if num then
callbackInput(property, num)
end
end,
string = function (property, gui)
callbackInput(property, gui.input.text)
end,
vector3 = function(property, gui)
local x,y,z = tonumber(gui.x.text),tonumber(gui.y.text),tonumber(gui.z.text)
if x and y and z then
callbackInput(property, vector3(x,y,z))
end
end,
vector2 = function(property, gui)
local x,y = tonumber(gui.x.text),tonumber(gui.y.text)
if x and y then
callbackInput(property, vector2(x,y))
end
end,
colour = function(property, gui)
local r,g,b = tonumber(gui.r.text),tonumber(gui.g.text),tonumber(gui.b.text)
if r and g and b then
callbackInput(property, colour:fromRGB(r,g,b))
end
end,
quaternion = function(property, gui)
--local x,y,z,w = tonumber(gui.x.text),tonumber(gui.y.text),tonumber(gui.z.text),tonumber(gui.w.text)
local x,y,z = tonumber(gui.x.text),tonumber(gui.y.text),tonumber(gui.z.text)
if x and y and z then
callbackInput(property, quaternion():setEuler(math.rad(x),math.rad(y),math.rad(z)))
end
end,
guiCoord = function(property, gui)
local sx,ox,sy,oy = tonumber(gui.scaleX.text),tonumber(gui.offsetX.text),tonumber(gui.scaleY.text),tonumber(gui.offsetY.text)
if sx and ox and sy and oy then
callbackInput(property, guiCoord(sx,ox,sy,oy))
end
end
}

View File

@ -0,0 +1,64 @@
-- these functions are responsible for updating the property editor gui
-- when the selections members are updated by 3rd party source
local themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
return {
block = function (instance, gui, value)
end,
boolean = function (instance, gui, value)
if engine.input.keyFocusedGui == gui.input then return end
gui.input.texture = value and "fa:s-toggle-on" or "fa:s-toggle-off"
themer.registerGui(gui.input, value and "successImage" or "errorImage")
end,
number = function (instance, gui, value)
if engine.input.keyFocusedGui == gui.input then return end
gui.input.text = string.format("%.3f", value)
end,
string = function (instance, gui, value)
if engine.input.keyFocusedGui == gui.input then return end
gui.input.text = value
end,
vector3 = function(instance, gui, value)
if engine.input.keyFocusedGui == gui.x or engine.input.keyFocusedGui == gui.y or engine.input.keyFocusedGui == gui.z then return end
gui.x.text = string.format("%.3f", value.x)
gui.y.text = string.format("%.3f", value.y)
gui.z.text = string.format("%.3f", value.z)
end,
vector2 = function(instance, gui, value)
if engine.input.keyFocusedGui == gui.x or engine.input.keyFocusedGui == gui.y then return end
gui.x.text = string.format("%.3f", value.x)
gui.y.text = string.format("%.3f", value.y)
end,
colour = function(instance, gui, value)
if engine.input.keyFocusedGui == gui.r or engine.input.keyFocusedGui == gui.g or engine.input.keyFocusedGui == gui.b then return end
gui.r.text = string.format("%.0f", value.r * 255)
gui.g.text = string.format("%.0f", value.g * 255)
gui.b.text = string.format("%.0f", value.b * 255)
gui.col.backgroundColour = value
end,
quaternion = function(instance, gui, value)
if engine.input.keyFocusedGui == gui.x or engine.input.keyFocusedGui == gui.y or engine.input.keyFocusedGui == gui.z then return end
local euler = value:getEuler()
gui.x.text = string.format("%.3f", math.deg(euler.x))
gui.y.text = string.format("%.3f", math.deg(euler.y))
gui.z.text = string.format("%.3f", math.deg(euler.z))
--gui.w.text = tostring(value.w)
end,
guiCoord = function(instance, gui, value)
if engine.input.keyFocusedGui == gui.scaleX or engine.input.keyFocusedGui == gui.offsetX or engine.input.keyFocusedGui == gui.scaleY or engine.input.keyFocusedGui == gui.offsetY then return end
gui.scaleX.text = tostring(value.scaleX)
gui.offsetX.text = tostring(value.offsetX)
gui.scaleY.text = tostring(value.scaleY)
gui.offsetY.text = tostring(value.offsetY)
end,
}

View File

@ -0,0 +1,144 @@
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local controller = {}
local modulePrefix = "tevgit:workshop/controllers/ui/components/propertyEditor/"
controller.window = ui.window(shared.workshop.interface, "Properties",
guiCoord(0, 260, 0, 400), -- size
guiCoord(1, -260, 0.5, -25), -- pos
true, -- dockable
true -- hidable
)
info = ui.create("guiTextBox", controller.window.content, {
size = guiCoord(1, 0, 0, 18),
text = "Nothing selected",
fontSize = 18
}, "backgroundText")
controller.scrollView = ui.create("guiScrollView", controller.window.content, {
size = guiCoord(1, 0, 1, -18),
position = guiCoord(0, 0, 0, 18),
backgroundAlpha = 0
}, "primary")
controller.eventHandlers = {}
local parseUpdates = require(modulePrefix .. "parseUpdates.lua")
local parseInputs = require(modulePrefix .. "parseInputs.lua")
local createInputs = require(modulePrefix .. "createInputs.lua")
local selection = require("tevgit:workshop/controllers/core/selection.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local function alphabeticalSorter(a, b) return a.property < b.property end
local excludePropertyList = {
physics = true, -- letting user changes this will break raycasts
doNotSerialise = true,
source = true
}
function controller.generateProperties()
for i, v in pairs(controller.eventHandlers) do v:disconnect() end
controller.eventHandlers = {}
if #selection.selection > 0 then
local firstObject = selection.selection[1]
local members = shared.workshop:getMembersOfObject(firstObject)
table.sort(members, alphabeticalSorter)
controller.scrollView:destroyAllChildren()
local y = 10
local propertiesCount = 0
for i, v in pairs(members) do
local value = firstObject[v.property]
local pType = type(value)
local readOnly = not v.writable
if not readOnly and pType ~= "function" and
not excludePropertyList[v.property] then
propertiesCount = propertiesCount + 1
local container = engine.construct("guiFrame",
controller.scrollView, {
name = "_" .. v.property,
backgroundAlpha = 0,
size = guiCoord(1, -10, 0, 20),
position = guiCoord(0, 0, 0, y),
cropChildren = false
})
local label = ui.create("guiTextBox", container, {
name = "label",
size = guiCoord(0.55, -15, 1, 0),
position = guiCoord(0, 0, 0, 0),
fontSize = 18,
backgroundAlpha = 1,
borderAlpha = 0,
text = v.property,
align = enums.align.topRight
}, "backgroundText")
local inputGui = nil
if createInputs[pType] then
inputGui = createInputs[pType](firstObject, v.property,
value)
else
inputGui = createInputs.default(firstObject, v.property,
value)
end
container.size = guiCoord(1, -10, 0, inputGui.size.offsetY)
container.zIndex = inputGui.zIndex
inputGui.parent = container
if parseUpdates[pType] then
parseUpdates[pType](firstObject, container.inputContainer,
value)
end
container.position = guiCoord(0, 5, 0, y)
y = y + container.size.offsetY + 3
end
end
info.text = type(firstObject) .. " has " .. tostring(propertiesCount) ..
" visible members."
table.insert(controller.eventHandlers,
firstObject:changed(
function(prop, val)
if parseUpdates[type(val)] then
local container = controller.scrollView["_" .. prop]
if container then
parseUpdates[type(val)](firstObject, container.inputContainer, val)
end
end
end)
)
local newSize = guiCoord(0, 0, 0, y)
if newSize ~= controller.scrollView.canvasSize then
controller.scrollView.viewOffset = vector2(0, 0)
end
controller.scrollView.canvasSize = newSize
else
info.text = "Nothing selected."
end
end
selection.registerCallback(controller.generateProperties)
return controller

View File

@ -0,0 +1,27 @@
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local window = ui.window(shared.workshop.interface,
"Run Lua",
guiCoord(0, 420, 0, 70), --size
guiCoord(0.5, -210, 0.5, -25), --pos
false, --dockable
true -- hidable
)
local runScriptBtn = ui.button(window.content, "Run", guiCoord(0, 50, 0, 30), guiCoord(0, 5, 0, 7), "primary")
local runScriptInput = ui.create("guiTextBox", window.content, {
size = guiCoord(1, -65, 0, 30),
position = guiCoord(0, 65, 0, 7),
readOnly = false,
wrap = true,
fontSize = 16
}, "secondary")
runScriptBtn:mouseLeftPressed(function ()
shared.workshop:loadString(runScriptInput.text)
runScriptInput.text = ""
end)
return window

View File

@ -0,0 +1,138 @@
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local autoSave = require("tevgit:workshop/controllers/ui/components/autoSave.lua")
local window = ui.window(shared.workshop.interface,
"Settings",
guiCoord(0, 620, 0, 500), --size
guiCoord(0.5, -310, 0.5, -250), --pos
false, --dockable
true -- hidable
)
local sideBar = ui.create("guiFrame", window.content, {
size = guiCoord(0.35, 3, 1, 6),
position = guiCoord(0, -3, 0, -3)
}, "primaryVariant")
local tabs = {}
local function addTab(tabName, tabFrame)
local tabBtn = ui.create("guiFrame", sideBar, {
size = guiCoord(1, -30, 0, 30),
position = guiCoord(0, 15, 0, 15 + (#tabs * 40)),
borderRadius = 3,
hoverCursor = "fa:s-hand-pointer"
}, "primary")
ui.create("guiTextBox", tabBtn, {
size = guiCoord(1, -12, 1, -6),
position = guiCoord(0, 6, 0, 3),
text = tabName,
handleEvents = false,
align = enums.align.middleLeft
}, "primaryText")
if #tabs > 0 then
tabFrame.visible = false
tabBtn.backgroundAlpha = 0
end
tabBtn:mouseLeftPressed(function ()
for _,v in pairs(tabs) do
v[1].visible = false
v[2].backgroundAlpha = 0
end
tabFrame.visible = true
tabBtn.backgroundAlpha = 1
end)
table.insert(tabs, {tabFrame, tabBtn})
end
local generalPage = ui.create("guiFrame", window.content, {
size = guiCoord(0.65, 0, 1, 0),
position = guiCoord(0.35, 0, 0, 0)
}, "background")
local syncThread = autoSave.Sync() -- Establish thread
local autoSaveToggle = ui.button(generalPage, autoSave.Enabled and "Disable Auto-Save" or "Enabled Auto-Save", guiCoord(0, 200, 0, 30), guiCoord(0, 10, 0, 10), "secondary")
autoSaveToggle:mouseLeftPressed(function()
autoSave.Enabled = not autoSave.Enabled
autoSaveToggle.label.text = autoSave.Enabled and "Disable Auto-Save" or "Enabled Auto-Save"
end)
addTab("General", generalPage)
local themePage = ui.create("guiScrollView", window.content, {
size = guiCoord(0.65, 0, 1, 0),
position = guiCoord(0.35, 0, 0, 0),
canvasSize = guiCoord(1,0,0,560)
}, "background")
require("tevgit:workshop/controllers/ui/components/themePreviewer.lua").parent = themePage
addTab("Theme", themePage)
if shared.developerMode then
local developmentPage = ui.create("guiScrollView", window.content, {
size = guiCoord(0.65, 0, 1, 0),
position = guiCoord(0.35, 0, 0, 0)
}, "background")
ui.create("guiTextBox", developmentPage, {
position = guiCoord(0, 15, 0, 15),
size = guiCoord(1, -30, 0, 20),
text = "This tab is mainly for developers of the workshop."
}, "backgroundText")
local createReload = ui.button(developmentPage, "Reload Workshop", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 50))
createReload:mouseLeftPressed(function ()
shared.workshop:reloadCreate()
end)
local shaderReload = ui.button(developmentPage, "Reload Shaders", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 90), "secondary")
shaderReload:mouseLeftPressed(function ()
shared.workshop:reloadShaders()
end)
local physicsDebugEnabled = false
local physicsAABBs = ui.button(developmentPage, "Enable Physics AABBs", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 130), "secondary")
physicsAABBs:mouseLeftPressed(function ()
physicsDebugEnabled = not physicsDebugEnabled
shared.workshop:setPhysicsDebug(physicsDebugEnabled)
physicsAABBs.label.text = physicsDebugEnabled and "Disable Physics AABBs" or "Enable Physics AABBs"
end)
local runScriptBtn = ui.button(developmentPage, "Run Lua", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 170), "secondary")
runScriptBtn:mouseLeftPressed(function ()
shared.windows.runLua.visible = not shared.windows.runLua.visible
end)
local printDump = ui.button(developmentPage, "Print Dump", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 210), "secondary")
printDump:mouseLeftPressed(function()
local dump = shared.workshop:apiDump()
print(engine.json:encode(dump))
end)
local physicsEnabled = engine.physics.running
local physicsToggle= ui.button(developmentPage, physicsEnabled and "Stop Simulating Physics" or "Simulate Physics", guiCoord(0, 190, 0, 30), guiCoord(0, 15, 0, 250), "secondary")
physicsToggle:mouseLeftPressed(function ()
physicsEnabled = not physicsEnabled
if physicsEnabled then
engine.physics:resume()
else
engine.physics:pause()
end
physicsToggle.label.text = physicsEnabled and "Stop Simulating Physics" or "Simulate Physics"
end)
addTab("Development", developmentPage)
--local dump = globalWorkshop:apiDump()
--print(globalEngine.json:encode(dump))
end
return window

View File

@ -0,0 +1,281 @@
-- Returns a frame with the different theme colours
-- Useful for overviewing a theme?
ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
shared = require("tevgit:workshop/controllers/shared.lua")
themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
colourPicker = require("tevgit:workshop/controllers/ui/components/colourPicker.lua")
parseInputs = require("tevgit:workshop/controllers/ui/components/propertyEditor/parseInputs.lua")
-- Overrides
local radiusNum = 0 -- defaults to flat
presets = {
{"default", "Classic (default)"},
{"black", "Tev Dark"},
{"white", "Tev Light"},
{"ow", "ow my eyes"},
{"custom", "Custom"}
}
container = ui.create("guiFrame", shared.workshop.interface, {
size = guiCoord(1, -10, 0, 560),
position = guiCoord(0, 10, 0, 10)
}, "background")
presetMenu = ui.create("guiFrame", container, {
position = guiCoord(0, 0, 0, 0),
size = guiCoord(0.5, 0, 0, 140),
borderRadius = 3
}, "primary")
importWindow = ui.window(shared.workshop.interface,
"Import Theme",
guiCoord(0, 420, 0, 230), --size
guiCoord(0.5, -210, 0.5, -25), --pos
false, --dockable
true -- hidable
)
importWindow.visible = false
importWindow.xIndex = 1000
frame = ui.create("guiFrame", importWindow.content, {
size = guiCoord(1, -20, 1, -60),
position = guiCoord(0, 10, 0, 10),
cropChildren = true,
backgroundAlpha = 0
})
importInput = ui.create("guiTextBox", frame, {
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0, 0),
readOnly = false,
wrap = true,
fontSize = 16,
zIndex = 100
}, "secondary")
function attemptCustom()
import = importInput.text
import = string.gsub(import,"\\","")
themer.setTheme(engine.json:decode(import))
end
importConfirmButton = ui.button(importWindow.content, "Import", guiCoord(0.5, 0, 0, 30), guiCoord(0.25, 0, 1, -40), "primary")
importButton = ui.create("guiButton", container, {
size = guiCoord(0.5, -20, 0, 30),
position = guiCoord(0.5, 10, 0, 40),
borderRadius = 3,
text = "Import Theme",
align = enums.align.middle
},"primaryVariant"):mouseLeftReleased(function()
importWindow.visible = true
end)
exportWindow = ui.window(shared.workshop.interface,
"Export Theme",
guiCoord(0, 420, 0, 230), --size
guiCoord(0.5, -210, 0.5, -25), --pos
false, --dockable
true -- hidable
)
exportWindow.visible = false
eframe = ui.create("guiFrame", exportWindow.content, {
size = guiCoord(1, -20, 1, -20),
position = guiCoord(0, 10, 0, 10),
cropChildren = true,
backgroundAlpha = 0
})
exportInput = ui.create("guiTextBox", eframe, {
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0, 0),
readOnly = false,
wrap = true,
fontSize = 16,
zIndex = 100
}, "secondary")
ui.create("guiTextBox", container, {
position = guiCoord(0.5, 10, 0, 80),
size = guiCoord(0.5, -20, 0, 30),
text = "Border Radius"
}, "backgroundText")
local borderRadiusInput = ui.create("guiTextBox", container, {
size = guiCoord(0.153, 0, 0.038, 0),
position = guiCoord(0.79, 10, 0, 80),
backgroundAlpha = 0.25,
readOnly = false,
multiline = false,
name = "input",
text = tostring(radiusNum),
align = enums.align.middle
}, "secondary")
borderRadiusInput:textInput(function(numText)
num = tonumber(numText)
if #numText == 0 then radiusNum = 0 return end
if string.find(numText,"%a") or string.find(numText,"%s") then borderRadiusInput:setText("0") radiusNum = 0 return end
if string.len(numText) > 5 then borderRadiusInput:setText(string.sub(numText,1,5)) radiusNum = 0 return end
if num < 0 then borderRadiusInput:setText("0") return end
radiusNum = tonumber(borderRadiusInput.text)
end)
THISISTHERESETBUTTON = ui.create("guiButton", container, {
size = guiCoord(0.5, -20, 0, 30),
position = guiCoord(0.5, 10, 0, 80),
borderRadius = 3,
text = "Export Theme",
align = enums.align.middle,
visible = false
},"primaryVariant")
THISISTHERESETBUTTON:mouseLeftReleased(function()
exportInput.text = shared.workshop:getSettings("customTheme")
exportWindow.visible = true
end)
customUI = ui.create("guiFrame", container, {
size = guiCoord(1, -10, 2, 0),
position = guiCoord(0, 0, 0, 150),
visible = false
}, "background")
theme = themer.getTheme()
function themeReload()
theme = null
theme = themer.getTheme()
end
function generateEditor()
customUI:destroyAllChildren()
local y = 0
for _,prop in pairs(themer.types) do
themeProperty = engine.construct("guiFrame", customUI, {
size = guiCoord(1, 0, 0, 40),
position = guiCoord(0, 0, 0, y),
backgroundAlpha = 0
})
ui.create("guiTextBox", themeProperty, {
size = guiCoord(1, -10, 0, 16),
position = guiCoord(0, 6, 0, 2),
text = prop,
fontSize = 16,
align = enums.align.middleLeft,
fontFile = "local:OpenSans-SemiBold.ttf"
}, "backgroundText")
count = 0
for _,v in pairs(theme[prop]) do if type(v) == "colour" then count = count + 1 end end
size = 1/count
i = 0
for k,v in pairs(theme[prop]) do
if type(v) == "colour" then
local ch,cs,cv = v:getHSV()
btn = ui.create("guiTextBox", themeProperty, {
size = guiCoord(size, -10, 0, 20),
position = guiCoord(size*i, 5, 0, 20),
text = k,
fontSize = 16,
align = enums.align.middle,
backgroundColour = v,
textColour = cv > 0.5 and colour:black() or colour:white(),
borderRadius = radiusNum, -- override this
borderColour = colour:black(),
borderAlpha = 0.3
}, prop)
btn:mouseLeftReleased(function()
colourPicker.prompt(theme[prop][k], function(c)
theme[prop][k] = c
themer.setTheme(theme)
exportInput.text = engine.json:decode(shared.workshop:getSettings("customTheme"))
themeReload()
end)
end)
i = i + 1
end
end
y = y + 44
end
end
function canvasSet(size)
container.parent.canvasSize = size
container.size = size
end
function makePresetMenu()
preset = shared.workshop:getSettings("themeType")
THISISTHERESETBUTTON.visible = false
customUI.visible = false
pcall(canvasSet, guiCoord(1, 0, 1, 0))
if shared.workshop:getSettings("themeType") == "custom" then
THISISTHERESETBUTTON.visible = true
customUI.visible = true
generateEditor()
pcall(canvasSet, guiCoord(1, 0, 0, 560))
end
presetMenu:destroyAllChildren()
local y = 0
for i = 1, #presets do
if preset == presets[i][1] then background = 1 else background = 0 end
preset = ui.create("guiButton", presetMenu, {
size = guiCoord(1, 0, 0, 20),
position = guiCoord(0, 0, 0, y),
backgroundAlpha = background,
text = " " .. presets[i][2],
borderRadius = 3
},"secondary"):mouseLeftReleased(function()
if presets[i][1] == "custom" then
shared.workshop:setSettings("themeType", presets[i][1])
shared.workshop:setSettings("customTheme", engine.json:encodeWithTypes(theme))
themer.setTheme(theme,radiusNum)
themeReload()
else
shared.workshop:setSettings("themeType", presets[i][1])
themer.setThemePreset(require("tevgit:workshop/controllers/ui/themes/" .. presets[i][1] .. ".lua"),radiusNum)
themeReload()
end
makePresetMenu()
end)
y = y + 20
end
end
importConfirmButton:mouseLeftReleased(function()
success, message = pcall(attemptCustom)
if success then
makePresetMenu()
importWindow.visible = false
importInput.text = ""
else
ui.prompt("The given theme is invalid, the theme has not been changed.")
end
end)
resetButton = ui.create("guiButton", container, {
size = guiCoord(0.5, -20, 0, 30),
position = guiCoord(0.5, 10, 0, 0),
borderRadius = 3,
text = "Reset Theme",
align = enums.align.middle
},"secondary"):mouseLeftReleased(function()
shared.workshop:setSettings("themeType", "default")
themer.setThemePreset(require("tevgit:workshop/controllers/ui/themes/default.lua"))
themeReload()
makePresetMenu()
end)
makePresetMenu()
return container

View File

@ -0,0 +1,96 @@
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local tools = require("tevgit:workshop/controllers/sidetools/main.lua")
local keybinder = require("tevgit:workshop/controllers/core/keybinder.lua")
-- main gui dock that is on left of the screen
-- it is moved by the dock controller IF windows are docked to the left side of the screen
local toolDock = engine.construct("guiFrame", shared.workshop.interface, {
name = "_toolBar",
cropChildren = false,
backgroundAlpha = 0,
position = guiCoord(0, 8, 0, 80),
})
local toolBar = ui.create("guiFrame", toolDock, {
name = "_toolBar",
size = guiCoord(0, 32, 0, 100),
position = guiCoord(0, 0, 0, 0),
backgroundAlpha = 0.8,
borderRadius = 4
}, "primary")
local activeTool = nil
local function toggleTool(toolName)
--engine.sounds:play("tevurl:sound/click.ogg") -- too much?
if activeTool ~= toolName then
if activeTool then
tools[activeTool].deactivate()
tools[activeTool].gui.icon.imageAlpha = 0.75
end
tools[toolName].activate()
tools[toolName].gui.icon.imageAlpha = 1
activeTool = toolName
else
tools[activeTool].deactivate()
tools[activeTool].gui.icon.imageAlpha = 0.75
activeTool = nil
end
end
local currentY = 5
local toolIndex = 0
for toolName, options in pairs(tools) do
-- used to assign a number keybind to each tool
toolIndex = toolIndex + 1
-- options is the table returned by the tool's module.
-- e.g. workshop/controllers/sidetools/hand.lua
local newTabBtn = ui.create("guiTextBox", toolBar, {
text = toolName,
position = guiCoord(0, 4, 0, currentY),
size = guiCoord(0, 24, 0, 24),
hoverCursor = "fa:s-hand-pointer"
}, "primaryText")
tools[toolName].gui = newTabBtn
if options.icon then
newTabBtn.text = ""
ui.create("guiImage", newTabBtn, {
name = "icon",
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0., 0),
texture = options.icon,
handleEvents = false,
imageAlpha = 0.75
}, "primaryImage")
end
local keybindText = ""
if toolIndex < 10 then
keybindText = " [" .. toolIndex .. "]"
keybinder:bind({
name = "Activate " .. options.name .. " tool",
key = enums.key["number"..toolIndex],
action = function() toggleTool(toolName) end
})
end
-- shows a tool tip if the user hovers over the button
ui.tooltip(newTabBtn, options.name .. keybindText)
newTabBtn:mouseLeftPressed(function ()
toggleTool(toolName)
end)
currentY = currentY + 32
end
toolBar.size = guiCoord(0, 32, 0, currentY - 5)

View File

@ -0,0 +1,157 @@
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local tabs = {
["Main"] = {
{"Open", "fa:s-folder-open", function()
shared.workshop:openFileDialogue()
end},
{"Save", "fa:s-save", function()
shared.workshop:saveGame()
end},
{"Save As", "fa:r-save", function()
shared.workshop:saveGameAsDialogue()
end},
{"Seperator"},
{"Properties", "fa:s-clipboard-list", function ()
shared.windows.propertyEditor.visible = not shared.windows.propertyEditor.visible
end},
{"Hierarchy", "fa:s-align-left", function ()
shared.windows.hierarchy.visible = not shared.windows.hierarchy.visible
end},
{"History", "fa:s-history", function ()
shared.windows.history.visible = not shared.windows.history.visible
end},
{"Seperator"},
{"Settings", "fa:s-cog", function ()
shared.windows.settings.visible = not shared.windows.settings.visible
end},
{"Test", "fa:s-play-circle", function ()
if not shared.workshop.gameFilePath or shared.workshop.gameFilePath == "" then
ui.prompt("Please save this game before testing.")
else
local content = engine.construct("guiTextBox", shared.workshop.interface, {
name = "_loadingTest",
backgroundAlpha = 0,
textAlpha = 0,
backgroundColour = colour:black(),
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0, 0),
zIndex = 5000,
fontSize = 24,
align = enums.align.middle,
text = "Uploading to Remote Testing Server\nServer: London"
})
engine.tween:begin(content, 1, {
backgroundAlpha = 0.95,
textAlpha = 1
}, "inOutQuad")
if not shared.workshop:remoteTestServer() then
content:destroy()
ui.prompt("Please save this game before testing.")
end
end
end},
}
}
if shared.developerMode then
tabs["Development"] = {
{"Reload", "fa:s-sync-alt", function()
shared.workshop:reloadCreate()
end},
{"Run Lua", "fa:s-chevron-right", function()
shared.windows.runLua.visible = not shared.windows.runLua.visible
end}
}
end
local topBar = ui.create("guiFrame", shared.workshop.interface, {
name = "topBar",
size = guiCoord(1, 0, 0, 22)
}, "primary")
local topBarSubMenu = ui.create("guiFrame", shared.workshop.interface, {
name = "topBarSubMenu",
size = guiCoord(1, 0, 0, 50),
position = guiCoord(0, 0, 0, 22)
}, "primaryVariant")
local currentX = 20
local guiTabs = {}
for tabName, options in pairs(tabs) do
local newTabBtn = ui.create("guiTextBox", topBar, {
text = tabName,
position = guiCoord(0, currentX, 0, 2),
fontSize = 20,
align = enums.align.middle,
hoverCursor = "fa:s-hand-pointer"
}, "primaryVariant")
local newSubMenu = engine.construct("guiFrame", topBarSubMenu, {
size = guiCoord(1, 0, 1, 0),
backgroundAlpha = 0
})
xpos = 12
for i,v in pairs(options) do
if v[1] == "Seperator" then
local seperator = ui.create("guiFrame", newSubMenu, {
size = guiCoord(0, 2, 0.6, 0),
position = guiCoord(0, xpos, 0.2, 0)
}, "primary")
xpos = xpos + 12
else
local newOption = ui.create("guiFrame", newSubMenu, {
size = guiCoord(0, 56, 0, 46),
position = guiCoord(0, xpos, 0, 2),
hoverCursor = "fa:s-hand-pointer"
}, "primaryVariant")
if type(v[3]) == "function" then
newOption:mouseLeftPressed(v[3])
end
ui.create("guiImage", newOption, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0, 18, 0, 6),
texture = v[2],
handleEvents = false
}, "primaryImage")
ui.create("guiTextBox", newOption, {
size = guiCoord(1, 0, 0, 16),
position = guiCoord(0, 0, 0, 30),
text = v[1],
handleEvents = false,
align = enums.align.middle,
fontSize = 15
}, "primaryText")
xpos = xpos + 62
end
end
newTabBtn:mouseLeftPressed(function ()
for btn, submenu in pairs(guiTabs) do
btn.backgroundAlpha = 0
submenu.visible = false
end
newSubMenu.visible = true
newTabBtn.backgroundAlpha = 1
end)
if currentX > 20 then
newSubMenu.visible = false
newTabBtn.backgroundAlpha = 0
end
local txtDim = newTabBtn.textDimensions
newTabBtn.size = guiCoord(0, txtDim.x + 20, 0, 20)
currentX = currentX + txtDim.x + 30
guiTabs[newTabBtn] = newSubMenu
end

View File

@ -0,0 +1,101 @@
-- Context Menu Helper
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local controller = {}
controller.currentContextMenu = nil
engine.input:mouseLeftReleased(function ()
if controller.currentContextMenu then
local focusedGui = engine.input.mouseFocusedGui
-- The mouse was clicked...
-- If there is no gui in focus,
-- or if the focused gui is not the context menu
-- ... we remove the context menu
if not focusedGui or (focusedGui ~= controller.currentContextMenu and not focusedGui:isDescendantOf(controller.currentContextMenu)) then
controller.currentContextMenu:destroy()
controller.currentContextMenu = nil
end
end
end)
-- Merely to demonstate the different options possible or to test the context menu helper.
controller.exampleOptions = {
{name = "Option 1", callback = function() print("callback") end},
{name = "Option 2"},
{name = "Option 3"}
}
-- Generates the menu and removes any old menus.
controller.generateMenu = function(options, position)
-- Destroy any preexisting context menu
if controller.currentContextMenu then
controller.currentContextMenu:destroy()
controller.currentContextMenu = nil
end
-- If the calling function did not provide a position for the context menu,
-- we default to the user's cursor position.
if not position or not type(position) == "guiCoord" then
position = guiCoord(0, engine.input.mousePosition.x, 0, engine.input.mousePosition.y)
end
-- create the context gui main frame, styled.
local menu = ui.create("guiFrame", shared.workshop.interface, {
name = "_contextMenu",
zIndex = 10000,
position = position
}, "primary")
-- so we can delete it later
controller.currentContextMenu = menu
local yPos = 6
-- create a gui for each option provided
for _,option in pairs(options) do
local btn = ui.create("guiTextBox", menu, {
size = guiCoord(0.8, 0, 0, 20),
position = guiCoord(0.1, 0, 0, yPos),
align = enums.align.middleLeft,
text = option.name,
textAlpha = 0.6
}, "primaryText")
if option.callback then
btn:mouseLeftReleased(function ()
option.callback()
menu:destroy()
controller.currentContextMenu = nil
end)
end
-- Improves accessibility
btn:mouseFocused(function ()
btn.textAlpha = 1
end)
btn:mouseUnfocused(function ()
btn.textAlpha = 0.6
end)
yPos = yPos + 26
end
-- make the main container the right size.
menu.size = guiCoord(0, 160, 0, yPos)
end
controller.bind = function(object, options)
if not object.mouseRightReleased then
return warn("Could not hook onto mouse event?!")
end
object:on("mouseRightReleased", function()
controller.generateMenu(options)
end)
end
return controller

View File

@ -0,0 +1,323 @@
local shared = require("tevgit:workshop/controllers/shared.lua")
local themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
local controller = {}
function roundToMultiple(number, multiple)
if multiple == 0 then
return number
end
return ((number % multiple) > multiple/2) and number + multiple - number%multiple or number - number%multiple
end
-- Used to sort objects by ZIndex.
-- This script uses the object's ZIndex to control the order in the dock
function numSorter(a,b)
return a < b
end
function zSorter(a,b)
return a.zIndex < b.zIndex
end
-- Currently only supposed to be called once,
-- it creates 'docks's that can hold other windows.
-- currently docks themselves are static and invisible,
-- this can easily be changed.
controller.setupDocks = function ()
if controller.docks then
-- delete old docks
for _,dock in pairs(controller.docks) do
for _,child in pairs(dock.children) do
child.parent = dock.parent
end
dock:destroy()
end
end
controller.docks = {
engine.construct("guiFrame", shared.workshop.interface, {
name = "_dockTop",
size = guiCoord(1, -500, 0, 250 - 76),
position = guiCoord(250, 0, 0, 76),
backgroundAlpha = 0,
handleEvents = false,
cropChildren = false,
}),
engine.construct("guiFrame", shared.workshop.interface, {
name = "_dockLeft",
size = guiCoord(0, 250, 1, -76),
position = guiCoord(0, 0, 0, 76),
backgroundAlpha = 0,
handleEvents = false,
cropChildren = false,
}),
engine.construct("guiFrame", shared.workshop.interface, {
name = "_dockBottom",
size = guiCoord(1, -500, 0, 250),
position = guiCoord(0, 250, 1, -250),
backgroundAlpha = 0,
handleEvents = false,
cropChildren = false,
}),
engine.construct("guiFrame", shared.workshop.interface, {
name = "_dockRight",
size = guiCoord(0, 265, 1, -76),
position = guiCoord(1, -265, 0, 76),
backgroundAlpha = 0,
handleEvents = false,
cropChildren = false,
})
}
end
controller.setupDocks()
-- Store info about a window,
-- such as their pre-docked size
local windowDetails = {}
-- Ran whenever a dock's contents is changed
local function dockCallback(dock, isPreviewing)
if dock.name == "_dockLeft" then
shared.workshop.interface["_toolBar"].position = (#dock.children > 0 or isPreviewing) and guiCoord(0, 258, 0, 80) or guiCoord(0, 8, 0, 80)
end
end
-- Adds a window to a dock
local function pushToDock(window, dock, slot)
local perWindow = 1 / (#dock.children + 1)
local isVertical = dock.absoluteSize.y > dock.absoluteSize.x
-- sort zIndexes
local children = dock.children
table.sort(children, zSorter)
local ii = 0
for i,v in pairs(children) do
if ii == slot then
ii = ii + 1
end
v.zIndex = ii
v.size = guiCoord(isVertical and 1 or perWindow, 0, isVertical and perWindow or 1, 0)
v.position = guiCoord(isVertical and 0 or (perWindow*(ii)), 0, isVertical and (perWindow*(ii)) or 0, 0)
ii = ii + 1
end
window.parent = dock
window.size = guiCoord(isVertical and 1 or perWindow, 0, isVertical and perWindow or 1, 0)
window.position = guiCoord(isVertical and 0 or (slot * perWindow), 0, isVertical and slot * perWindow or 0, 0)
window.zIndex = slot
dockCallback(dock)
end
-- Properly orders windows within a dock
-- useful when a window is removed
local function orderDock(dock)
local perWindow = 1 / #dock.children
local isVertical = dock.absoluteSize.y > dock.absoluteSize.x
-- sort zIndexes
local children = dock.children
table.sort(children, zSorter)
local ii = 0
for i,v in pairs(children) do
v.zIndex = ii
v.size = guiCoord(isVertical and 1 or perWindow, 0, isVertical and perWindow or 1, 0)
v.position = guiCoord(isVertical and 0 or (perWindow*(ii)), 0, isVertical and (perWindow*(ii)) or 0, 0)
ii = ii + 1
end
dockCallback(dock)
end
-- returns the parent dock of a window if any
local function isInDock(window)
for _,v in pairs(controller.docks) do
if window:isDescendantOf(v) then
return v
end
end
end
controller.saveDockSettings = function()
-- Save dock to workshop settings
local settings = {}
for _,v in pairs(controller.docks) do
settings[v.name] = {}
local children = v.children
table.sort(children, zSorter)
for _,vv in pairs(children) do
settings[v.name][vv.name] = vv.zIndex
end
shared.workshop:setSettings("workshopDocks", settings)
end
end
-- used if docks haven't been saved before:
local defaultSettings = {
["_dockRight"] = {
["Hierarchy"] = 0,
["Properties"] = 1
}
}
controller.loadDockSettings = function()
-- Load Dock from settings
local settings = shared.workshop:getSettings("workshopDocks")
if not settings then
settings = defaultSettings
end
for dockName, windows in pairs(settings) do
local dock = shared.workshop.interface:hasChild(dockName)
if dock then
for windowName, zIndex in pairs(windows) do
local win = shared.workshop.interface:hasChild(windowName)
if win then
if not windowDetails[win] then
windowDetails[win] = {zIndex = win.zIndex, size = win.size}
end
win.zIndex = zIndex
win.parent = dock
end
end
end
end
for _,v in pairs(controller.docks) do
orderDock(v)
end
end
controller.undock = function (window)
local dock = isInDock(window)
if dock then
window.parent = dock.parent
orderDock(dock)
if windowDetails[window] then
window.size = windowDetails[window].size
end
end
end
-- Invoked by a 3rd party script when user begins dragging window.
controller.beginWindowDrag = function(window, dontDock)
-- change window appeareance (mainly for debugging) to make it clear this window is on the move.
if not windowDetails[window] then
windowDetails[window] = {zIndex = window.zIndex, size = window.size}
else
window.size = windowDetails[window].size
end
window.zIndex = 99
-- offset used for dragging
local offset = window.absolutePosition - engine.input.mousePosition
-- undock window
controller.undock(window)
local previewer = engine.construct("guiFrame", shared.workshop.interface, {
handleEvents = false,
visible = false,
backgroundAlpha = 0.75
})
themer.registerGui(previewer, "primary")
local isOverDock, slot;
local lastDock;
-- yield until user releases window drag
while engine.input:isMouseButtonDown(enums.mouseButton.left) do
local mPos = engine.input.mousePosition
local newpos = mPos + offset
window.position = guiCoord(0, newpos.x, 0, newpos.y)
isOverDock = false
if not dontDock then
for _,dock in pairs(controller.docks) do
-- the user's cursor is in this dock.
if mPos.x > dock.absolutePosition.x and
mPos.x < dock.absolutePosition.x + dock.absoluteSize.x and
mPos.y > dock.absolutePosition.y and
mPos.y < dock.absolutePosition.y + dock.absoluteSize.y then
local perWindow = 1 / (#dock.children + 1)
local perWindowSize = perWindow * dock.absoluteSize
local isVertical = dock.absoluteSize.y > dock.absoluteSize.x
local pws = (isVertical and perWindowSize.y or perWindowSize.x)
local m = (isVertical and mPos.y or mPos.x)
local dockPosition = (isVertical and dock.absolutePosition.y or dock.absolutePosition.x)
local dockSize = (isVertical and dock.absoluteSize.y or dock.absoluteSize.x)
local mouseScaled = (m - dockPosition) / dockSize
slot = math.clamp((roundToMultiple(mouseScaled, perWindow) / perWindow), 0, (#dock.children))
isOverDock = dock
previewer.visible = true
previewer.size = guiCoord(0, isVertical and dock.absoluteSize.x or pws, 0, isVertical and pws or dock.absoluteSize.y)
previewer.position = guiCoord(0, isVertical and dock.absolutePosition.x or dock.absolutePosition.x + (slot * pws), 0, isVertical and dock.absolutePosition.y + (slot * pws) or dock.absolutePosition.y)
local ii = 0
local children = dock.children
table.sort(children, zSorter)
for i,v in pairs(children) do
if ii == slot then
ii = ii + 1
end
v.size = guiCoord(isVertical and 1 or perWindow, 0, isVertical and perWindow or 1, 0)
v.position = guiCoord(isVertical and 0 or (perWindow*(ii)), 0, isVertical and (perWindow*(ii)) or 0, 0)
ii = ii + 1
end
dockCallback(dock, true)
end
end
end
if (lastDock ~= isOverDock) then
if lastDock then
orderDock(lastDock) -- reorder old dock we were hovering
end
lastDock = isOverDock
end
if not isOverDock and previewer.visible then
previewer.visible = false
end
wait()
end
if (lastDock and lastDock ~= isOverDock) then
orderDock(lastDock) -- reorder old dock we were hovering
end
previewer:destroy()
-- reset Window appearance
window.zIndex = windowDetails[window].zIndex
if isOverDock then
pushToDock(window, isOverDock, slot)
else
windowDetails[window] = nil
end
-- save dock
controller.saveDockSettings()
end
return controller

View File

@ -0,0 +1,130 @@
-- Copyright 2020 Teverse.com
-- Responsible for managing the aesthetics of different UI elements
local shared = require("tevgit:workshop/controllers/shared.lua")
local currentTheme = nil
local registeredGuis = {}
local themeType = shared.workshop:getSettings("themeType")
customTheme = shared.workshop:getSettings("customTheme")
if themeType == "custom" then
currentTheme = engine.json:decode(customTheme)
elseif themeType == "black" then
currentTheme = require("tevgit:workshop/controllers/ui/themes/black.lua")
elseif themeType == "white" then
currentTheme = require("tevgit:workshop/controllers/ui/themes/white.lua")
elseif themeType == "ow" then
currentTheme = require("tevgit:workshop/controllers/ui/themes/ow.lua")
else
currentTheme = require("tevgit:workshop/controllers/ui/themes/default.lua")
shared.workshop:setSettings("themeType", "default")
end
local function themeriseGui(gui,...)
-- Grab the gui's style name set in the "registerGui" func
local styleName = registeredGuis[gui]
local args = nil
if #{...} > 0 then
args = {...}
args = args[1]
end
-- get the style's properties from the current theme
local style = currentTheme[styleName]
if not style then
style = {}
end
-- apply the theme's properties to the gui
for property, value in pairs(style) do
if gui[property] and gui[property] ~= value then
gui[property] = value
end
if args then
gui["borderRadius"] = args[1] or 0
end
end
end
return {
types = {
primary = "primary",
primaryVariant = "primaryVariant",
primaryText = "primaryText",
primaryImage = "primaryImage",
secondary = "secondary",
secondaryVariant = "secondaryVariant",
secondaryText = "secondaryText",
error = "error",
errorText = "errorText",
background = "background",
backgroundText = "backgroundText",
},
themeriseGui = themeriseGui,
registerGui = function(gui, style, ...)
-- set the gui's style and themerise it.
registeredGuis[gui] = style
local args = {...} -- contains overrides
if args then
themeriseGui(gui,args)
elseif not args then
themeriseGui(gui)
end
end,
setTheme = function(theme,...)
-- change the current theme AND re-themerise all guis
currentTheme = theme
args = {...} -- contains overrides
for gui,v in pairs(registeredGuis) do
if args then
themeriseGui(gui,args)
else
themeriseGui(gui)
end
end
-- Save the theme
shared.workshop:setSettings("themeType", "custom")
shared.workshop:setSettings("customTheme", engine.json:encodeWithTypes(currentTheme))
end,
resetTheme = function(theme)
-- change the current theme AND re-themerise all guis
currentTheme = require("tevgit:workshop/controllers/ui/themes/default.lua")
for gui,v in pairs(registeredGuis) do
themeriseGui(gui)
end
-- Save the theme
shared.workshop:setSettings("themeType", "default")
shared.workshop:setSettings("customTheme", null)
end,
setThemePreset = function(theme,...)
-- change the current theme AND re-themerise all guis
currentTheme = theme
local args = {...} -- contains overrides
for gui,v in pairs(registeredGuis) do
if args then
themeriseGui(gui,args)
elseif not args then
themeriseGui(gui)
end
end
shared.workshop:setSettings("customTheme", null)
end,
getTheme = function()
return currentTheme
end
}

View File

@ -0,0 +1,227 @@
-- Copyright 2020 Teverse.com
-- This script includes shorcuts for creating UIs.
-- Any interface created here will be properly themed.
local themer = require("tevgit:workshop/controllers/ui/core/themer.lua")
local dock = require("tevgit:workshop/controllers/ui/core/dock.lua")
local shared = require("tevgit:workshop/controllers/shared.lua")
local create = function(className, parent, properties, style)
if not parent then
parent = shared.workshop.interface
end
local gui = engine.construct(className, parent, properties)
themer.registerGui(gui, style and style or "default")
return gui
end
local activeTooltip = nil
return {
tooltip = function ( gui, text, delay )
if gui:isA("guiBase") then
if not delay then delay = 0.4 end
return gui:on("mouseFocused", function ()
local tooltip = create("guiTextBox", shared.workshop.interface, {
position = guiCoord(0, engine.input.mousePosition.x + 16, 0, engine.input.mousePosition.y),
text = text,
fontSize = 16,
align = enums.align.middle,
borderRadius = 0,
borderAlpha = 1,
zIndex = 5000,
handleEvents = false,
visible = false
}, "primaryVariant")
local textDimensions = tooltip.textDimensions
tooltip.size = guiCoord(0, textDimensions.x + 10, 0, textDimensions.y + 4)
gui:once("mouseUnfocused", function()
tooltip:destroy()
end)
wait(delay)
if tooltip and tooltip.alive then
tooltip.visible = true
end
end)
end
end,
create = create,
button = function(parent, text, size, position, theme)
if not theme then theme = "primary" end
local btn = create("guiFrame", parent, {
size = size,
position = position,
borderRadius = 0,
hoverCursor = "fa:s-hand-pointer"
}, theme)
create("guiTextBox", btn, {
name = "label",
size = guiCoord(1, -12, 1, -6),
position = guiCoord(0, 6, 0, 3),
text = text,
handleEvents = false,
align = enums.align.middle,
}, theme .. "Text")
return btn
end,
-- if closable is true OR a function, a close button will appear in the title bar.
-- when clicked, if closable is a function, it is fired after hiding the window.
window = function(parent, title, size, position, dockable, closable, dragable)
local container = create("guiFrame", parent, {
size = size,
name = title,
position = position,
cropChildren = false,
borderColour = colour:fromRGB(55, 59, 64),
borderWidth = 2,
borderAlpha = 1,
}, themer.types.background)
container:on("changed", function (property, value)
if property == "visible" and not value then
dock.undock(container) -- just in case
end
end)
local titleBar = create("guiFrame", container, {
name = "titleBar",
position = guiCoord(0, -1, 0, -4),
size = guiCoord(1, 2, 0, 25),
borderRadius = 0,
hoverCursor = "fa:s-hand-pointer"
}, themer.types.primary)
titleBar:mouseLeftPressed(function ()
if dragable == false then return end -- Backwards compatibility
dock.beginWindowDrag(container, not dockable)
end)
-- create this to hide radius on bottom of titlebar
create("guiFrame", titleBar, {
size = guiCoord(1, 0, 0, 3),
position = guiCoord(0, 0, 1, -3)
}, themer.types.primary)
create("guiTextBox", titleBar, {
name = "textBox",
size = guiCoord(1, -12, 0, 20),
position = guiCoord(0, 6, 0, 2),
text = title,
handleEvents = false
}, themer.types.primaryText)
if closable then
local closeBtn = create("guiImage", titleBar, {
position = guiCoord(1, -22, 0, 3),
size = guiCoord(0, 19, 0, 19),
texture = "fa:s-window-close",
imageAlpha = 0.5,
hoverCursor = "fa:s-hand-pointer"
}, "primaryImage")
closeBtn:mouseLeftReleased(function ()
container.visible = false
if type(closable) == "function" then
closable()
end
end)
end
local content = engine.construct("guiFrame", container, {
name = "content",
backgroundAlpha = 0,
size = guiCoord(1, -12, 1, -27),
position = guiCoord(0, 3, 0, 24),
cropChildren = false,
})
return container
end,
-- Creates a full screen prompt
-- Prevents user from doing anything until they acknowledge the prompt
-- if callback is nil, the function yields until the user continues
-- if callback is a function, it is ran when the user continues
prompt = function ( message, callback )
local content = create("guiFrame", shared.workshop.interface, {
name = "_prompt",
backgroundAlpha = 0.9,
backgroundColour = colour:black(),
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0, 0),
zIndex = 5000
}, "background")
if shared.developerMode then
content.handleEvents = false
create("guiTextBox", content, {
name = "disclaimer",
size = guiCoord(1, 0, 0, 14),
position = guiCoord(0, 0, 1, -14),
text = "Developer mode is enabled, this prompt has not captured your mouse input, bg opacity is also decreased.",
handleEvents = false,
align = enums.align.middle,
fontSize = 14
}, "backgroundText")
content.backgroundAlpha = 0.75
end
local container = create("guiFrame", content, {
size = guiCoord(0.4, 0, 0.4, 0),
position = guiCoord(0.3, 0, 0.3, 0),
cropChildren = false,
borderRadius = 0,
handleEvents = false
}, themer.types.primary)
local text = create("guiTextBox", container, {
name = "disclaimer",
size = guiCoord(1, -20, 1, -20),
position = guiCoord(0, 10, 0, 10),
text = message,
handleEvents = false,
align = enums.align.middle,
fontSize = 21
}, "primaryText")
local textDimensions = text.textDimensions
container.size = guiCoord(0, textDimensions.x + 20, 0, textDimensions.y + 20)
container.position = guiCoord(0.5, -(textDimensions.x + 20)/2, 0.5, -(textDimensions.y + 20)/2)
local continue = create("guiTextBox", content, {
size = guiCoord(0, textDimensions.x + 20, 0, 40),
position = guiCoord(0.5, -(textDimensions.x + 20)/2, 0.5, (textDimensions.y/2 + 5)),
cropChildren = false,
zIndex = 2,
text = "Continue",
align = enums.align.middle,
fontFile = "local:OpenSans-Bold.ttf"
}, themer.types.primaryVariant)
if not callback then
local acknowledged = false
continue:once("mouseLeftPressed", function() acknowledged = true end)
repeat wait() until acknowledged
content:destroy()
return true
else
continue:once("mouseLeftPressed", function()
if type(callback) == "function" then
callback()
end
content:destroy()
end)
end
end
}

View File

@ -0,0 +1,28 @@
-- Copyright 2020 Teverse.com
-- Responsible for creating the workshop interface
local shared = require("tevgit:workshop/controllers/shared.lua")
local ui = require("tevgit:workshop/controllers/ui/core/ui.lua")
require("tevgit:workshop/controllers/ui/components/topBar.lua")
require("tevgit:workshop/controllers/ui/components/toolBar.lua")
require("tevgit:workshop/controllers/ui/components/inserter.lua")
shared.windows.settings = require("tevgit:workshop/controllers/ui/components/settings.lua")
shared.windows.settings.visible = false -- Dont show the window on start, thats annoying
shared.windows.runLua = require("tevgit:workshop/controllers/ui/components/runLua.lua")
shared.windows.runLua.visible = false
shared.windows.propertyEditor = require("tevgit:workshop/controllers/ui/components/propertyEditor/window.lua").window
shared.windows.hierarchy = require("tevgit:workshop/controllers/ui/components/hierarchy.lua").window
shared.windows.history = require("tevgit:workshop/controllers/ui/components/historyUi.lua")
shared.windows.history.visible = false
require("tevgit:workshop/controllers/ui/core/dock.lua").loadDockSettings()
if not shared.developerMode then
ui.prompt("This is community driven software\nIt does not resemble the final product in anyway.", true)
end

View File

@ -0,0 +1,76 @@
-- Copyright 2020 Teverse.com
return {
primary = {
backgroundColour = colour:fromRGB(8, 8, 8),
textColour = colour:fromRGB(255, 255, 255),
},
primaryVariant = {
backgroundColour = colour:fromRGB(16, 16, 16),
textColour = colour:fromRGB(255, 255, 255),
},
primaryText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
primaryImage = {
imageColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
secondary = {
backgroundColour = colour:fromRGB(32, 32, 32),
textColour = colour:fromRGB(255, 255, 255),
},
secondaryVariant = {
backgroundColour = colour:fromRGB(48, 48, 48),
textColour = colour:fromRGB(255, 255, 255),
},
secondaryText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
secondaryImage = {
imageColour = colour:fromRGB(255, 255, 25),
backgroundAlpha = 0
},
error = {
backgroundColour = colour:fromRGB(176, 0, 16),
textColour = colour:fromRGB(255, 255, 255),
},
errorText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
errorImage = {
imageColour = colour:fromRGB(176, 100, 116),
backgroundAlpha = 0
},
success = {
backgroundColour = colour:fromRGB(0, 176, 16),
textColour = colour:fromRGB(255, 255, 255),
},
successText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
successImage = {
imageColour = colour:fromRGB(100, 176, 116),
backgroundAlpha = 0
},
background = {
backgroundColour = colour:fromRGB(64, 64, 64),
textColour = colour:fromRGB(255, 255, 255),
},
backgroundText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
backgroundImage = {
imageColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
}
}

View File

@ -0,0 +1,76 @@
-- Copyright 2020 Teverse.com
return {
primary = {
backgroundColour = colour:fromRGB(44, 47, 51),
textColour = colour:fromRGB(255, 255, 255),
},
primaryVariant = {
backgroundColour = colour:fromRGB(55, 59, 64),
textColour = colour:fromRGB(255, 255, 255),
},
primaryText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
primaryImage = {
imageColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
secondary = {
backgroundColour = colour:fromRGB(67, 92, 125),
textColour = colour:fromRGB(255, 255, 255),
},
secondaryVariant = {
backgroundColour = colour:fromRGB(78, 107, 145),
textColour = colour:fromRGB(255, 255, 255),
},
secondaryText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
secondaryImage = {
imageColour = colour:fromRGB(215, 215, 215),
backgroundAlpha = 0
},
error = {
backgroundColour = colour:fromRGB(176, 0, 32),
textColour = colour:fromRGB(255, 255, 255),
},
errorText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
errorImage = {
imageColour = colour:fromRGB(176, 100, 132),
backgroundAlpha = 0
},
success = {
backgroundColour = colour:fromRGB(0, 176, 32),
textColour = colour:fromRGB(255, 255, 255),
},
successText = {
textColour = colour:fromRGB(255, 255, 255),
backgroundAlpha = 0
},
successImage = {
imageColour = colour:fromRGB(100, 176, 132),
backgroundAlpha = 0
},
background = {
backgroundColour = colour:fromRGB(169, 170, 171),
textColour = colour:fromRGB(0, 0, 0),
},
backgroundText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
backgroundImage = {
imageColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
}
}

View File

@ -0,0 +1,76 @@
-- Copyright 2020 Teverse.com
return {
primary = {
backgroundColour = colour:fromRGB(255,0,0),
textColour = colour:fromRGB(255,255,0),
},
primaryVariant = {
backgroundColour = colour:fromRGB(255,255,255),
textColour = colour:fromRGB(0, 255, 0),
},
primaryText = {
textColour = colour:fromRGB(0, 255, 255),
backgroundAlpha = 0
},
primaryImage = {
imageColour = colour:fromRGB(255, 0, 0),
backgroundAlpha = 0
},
secondary = {
backgroundColour = colour:fromRGB(255, 255, 0),
textColour = colour:fromRGB(255, 255, 255),
},
secondaryVariant = {
backgroundColour = colour:fromRGB(0, 255, 0),
textColour = colour:fromRGB(0, 255, 255),
},
secondaryText = {
textColour = colour:fromRGB(255, 0, 0),
backgroundAlpha = 0
},
secondaryImage = {
imageColour = colour:fromRGB(255, 255, 0),
backgroundAlpha = 0
},
error = {
backgroundColour = colour:fromRGB(255,255,255),
textColour = colour:fromRGB(0, 255, 0),
},
errorText = {
textColour = colour:fromRGB(0, 255, 255),
backgroundAlpha = 0
},
errorImage = {
imageColour = colour:fromRGB(255, 0, 0),
backgroundAlpha = 0
},
success = {
backgroundColour = colour:fromRGB(255, 255, 0),
textColour = colour:fromRGB(255, 0, 0),
},
successText = {
textColour = colour:fromRGB(255, 255, 0),
backgroundAlpha = 0
},
successImage = {
imageColour = colour:fromRGB(255,255,255),
backgroundAlpha = 0
},
background = {
backgroundColour = colour:fromRGB(0, 255, 0),
textColour = colour:fromRGB(0, 255, 255),
},
backgroundText = {
textColour = colour:fromRGB(255, 0, 0),
backgroundAlpha = 0
},
backgroundImage = {
imageColour = colour:fromRGB(255,255,255),
backgroundAlpha = 0
}
}

View File

@ -0,0 +1,76 @@
-- Copyright 2020 Teverse.com
return {
primary = {
backgroundColour = colour:fromRGB(255, 255, 255),
textColour = colour:fromRGB(0, 0, 0),
},
primaryVariant = {
backgroundColour = colour:fromRGB(239, 239, 239),
textColour = colour:fromRGB(0, 0, 0),
},
primaryText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
primaryImage = {
imageColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
secondary = {
backgroundColour = colour:fromRGB(223, 223, 223),
textColour = colour:fromRGB(0, 0, 0),
},
secondaryVariant = {
backgroundColour = colour:fromRGB(207, 207, 207),
textColour = colour:fromRGB(0, 0, 0),
},
secondaryText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
secondaryImage = {
imageColour = colour:fromRGB(0, 0, 25),
backgroundAlpha = 0
},
error = {
backgroundColour = colour:fromRGB(176, 0, 239),
textColour = colour:fromRGB(0, 0, 0),
},
errorText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
errorImage = {
imageColour = colour:fromRGB(176, 100, 1239),
backgroundAlpha = 0
},
success = {
backgroundColour = colour:fromRGB(0, 176, 239),
textColour = colour:fromRGB(0, 0, 0),
},
successText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
successImage = {
imageColour = colour:fromRGB(100, 176, 1239),
backgroundAlpha = 0
},
background = {
backgroundColour = colour:fromRGB(191, 191, 191),
textColour = colour:fromRGB(0, 0, 0),
},
backgroundText = {
textColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
},
backgroundImage = {
imageColour = colour:fromRGB(0, 0, 0),
backgroundAlpha = 0
}
}

View File

@ -1,22 +0,0 @@
-- Copyright 2020- Teverse.com
-- Used to share variables between scripts
return {
workshop = nil, -- Holds workshop instance and is set in main.lua
user = nil, -- Holds user instance and is set in main.lua
developerMode = false, -- Holds the developer_mode boolean and is set in main.lua
sideBarPageDefault = nil, -- Holds the default sidebar page (view) as a string and is set in topbarInterface.lua
sideBarPageActive = nil, -- Holds the current active sidebar page (view) as a string and is set in topbarInterface.lua
defaultColours = { -- Default colors used for theming UI components (~\library\ui\components)
primary = colour:fromRGB(112, 112, 112),
secondary = colour:fromRGB(239, 239, 239),
background = colour:fromRGB(33, 33, 33),
red = colour:fromRGB(255, 82, 82),
green = colour:fromRGB(105, 240, 174),
yellow = colour:fromRGB(255, 215, 64),
blue = colour:fromRGB(68, 138, 255),
orange = colour:fromRGB(255, 171, 64),
purple = colour:fromRGB(124, 77, 255),
white = colour:fromRGB(255, 255, 255)
}
}

View File

@ -1,133 +0,0 @@
-- Copyright 2020- Teverse
-- This script constructs (or builds) the tooltip component
local globals = require("tevgit:workshop/library/globals.lua") -- globals; variables or instances that can be shared between files
return {
construct = function(orientation, element, text, ...)
--[[
@Description
Constructor method that initializes the tooltip instance.
@Params
String, orientation
UiBase, element
String, text
{...}, overrides
@Returns
Instance, tooltip
]]--
local data = {}
self = data
-- Only for horizontal orientation
local args = {...} -- Hold overrides
local positionOverride = args[1] or guiCoord(0, 0, 0, 0) -- If not specified, default to guiCoord(0, 0, 0, 0)
if orientation == "vertical" then -- If orientation is specified to "vertical"
local container = engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(0.1, 0, 0.1, 0),
position = element.position+guiCoord(-0.02, 0, -0.01, 0),
backgroundColour = globals.defaultColours.red,
visible = false,
zIndex = 200,
backgroundAlpha = 0
})
engine.construct("guiImage", container, {
size = guiCoord(0, 48, 0, 48),
position = guiCoord(0.33, 0, -0.15, 0),
texture = "fa:s-caret-up",
imageColour = globals.defaultColours.secondary,
backgroundColour = globals.defaultColours.red,
backgroundAlpha = 0
})
local bodyContainer = engine.construct("guiFrame", container, {
size = guiCoord(0.95, 0, 0.4, 0),
position = guiCoord(0.025, 0, 0.23, 0),
backgroundColour = globals.defaultColours.white,
borderAlpha = 1,
borderRadius = 5,
borderWidth = 3,
borderColour = globals.defaultColours.secondary
})
engine.construct("guiImage", bodyContainer, {
size = guiCoord(0, 16, 0, 16),
position = guiCoord(0.04, 0, 0.25, 0),
texture = "fa:s-info-circle",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
engine.construct("guiTextBox", bodyContainer, {
size = guiCoord(0.82, 0, 1, 0),
position = guiCoord(0.15, 0, 0, 0),
text = text,
fontSize = 16,
textColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
align = enums.align.middle,
wrap = true
})
self.display = function() container.visible = true end -- Display tooltip method
self.hide = function() container.visible = false end -- Hide tooltip method
elseif orientation == "horizontal" then -- If orientation is specified to "horizontal"
local container = engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(0.13, 0, 0.05, 0),
position = (element.position+guiCoord(-0.22, 0, 0.24, 0))+positionOverride, -- Shorthand positioning
backgroundColour = globals.defaultColours.red,
visible = false,
zIndex = 200,
backgroundAlpha = 0
})
engine.construct("guiImage", container, {
size = guiCoord(0, 48, 0, 48),
position = guiCoord(-0.03, 0, -0.06, 0),
texture = "fa:s-caret-left",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.red,
backgroundAlpha = 0
})
local bodyContainer = engine.construct("guiFrame", container, {
size = guiCoord(0.8, 0, 0.9, 0),
position = guiCoord(0.133, 0, 0.05, 0),
backgroundColour = globals.defaultColours.white,
borderAlpha = 1,
borderRadius = 5,
borderWidth = 3,
borderColour = globals.defaultColours.primary
})
engine.construct("guiImage", bodyContainer, {
size = guiCoord(0, 16, 0, 16),
position = guiCoord(0.05, 0, 0.3, 0),
texture = "fa:s-info-circle",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
engine.construct("guiTextBox", bodyContainer, {
size = guiCoord(0.82, 0, 1, 0),
position = guiCoord(0.15, 0, 0, 0),
text = text,
fontSize = 16,
textColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
align = enums.align.middle,
wrap = true
})
self.display = function() container.visible = true end -- Display tooltip method
self.hide = function() container.visible = false end -- Hide tooltip method
end
return data
end
}

View File

@ -1,158 +0,0 @@
-- Copyright 2020- Teverse
-- This script constructs (or builds) the sidebar controller
local globals = require("tevgit:workshop/library/globals.lua") -- globals; variables or instances that can be shared between files
local toolTip = require("tevgit:workshop/library/ui/components/toolTip.lua") -- UI component
return {
construct = function(idValue)
--[[
@Description
Constructor method that initializes the sidebar instance.
@Params
String, idValue
@Returns
Instance, sidebar
]]--
local data = {}
self = data
self.id = idValue -- Unique Indentifier
self.pages = {} -- Where we store our pages for sidebar
engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(0.04, 0, 0.015, 0),
position = guiCoord(0, 0, 0.05, 0),
backgroundColour = globals.defaultColours.secondary,
})
engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(0.04, 0, 0.015, 0),
position = guiCoord(0, 0, 0.24, 0),
backgroundColour = globals.defaultColours.secondary,
})
local toolsContainer = engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(0.04, 0, 0.18, 0),
position = guiCoord(0, 0, 0.065, 0),
backgroundColour = globals.defaultColours.white,
})
local selectTool = engine.construct("guiImage", toolsContainer, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.25, 0, 0.1, 0),
texture = "fa:s-location-arrow",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local moveTool = engine.construct("guiImage", toolsContainer, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.25, 0, 0.32, 0),
texture = "fa:s-arrows-alt-h",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local rotateTool = engine.construct("guiImage", toolsContainer, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.25, 0, 0.54, 0),
texture = "fa:s-sync",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local sizeTool = engine.construct("guiImage", toolsContainer, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.25, 0, 0.76, 0),
texture = "fa:s-expand",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local moreToolsContainer = engine.construct("guiFrame", globals.workshop.interface, {
name = "moreToolsContainer",
size = guiCoord(0.04, 0, 1, 0),
position = guiCoord(0, 0, 0.255, 0),
backgroundColour = globals.defaultColours.white,
})
self.registerPage = function(pageName)
--[[
@Description
Registers page to sidebar instance.
@Params
String, pageName
@Returns
guiFrame, page
]]--
local zIndexRange
if pageName == "Default" then -- Default page zIndex is set lower than other pages
zIndexRange = 100
else
zIndexRange = 101
end
local iconContainer = engine.construct("guiFrame", moreToolsContainer, {
name = pageName,
size = guiCoord(1, 0, 1, 0),
position = guiCoord(0, 0, 0, 0),
backgroundColour = globals.defaultColours.white,
zIndex = zIndexRange,
visible = false
})
return iconContainer
end
self.registerIcon = function(page, name, icon, tooltip, callback, ...)
--[[
@Description
Registers icon to page instance.
@Params
Instance, page
String, name
String, icon
String, tooltip
Method, callback
{...}, overrides
@Returns
Void, null, nil
]]--
local args = {...} -- Holds overrides
local xPositionOverride = args[1] or 0 -- Override if specified, else 0
local positionToolTipOverride = args[2] or guiCoord(0, 0, 0, 0) -- Override if specified, else guiCoord(0, 0, 0, 0)
local iconImage = engine.construct("guiImage", page, {
name = name,
size = guiCoord(0, 20, 0, 20),
position = guiCoord((0.25+xPositionOverride), 0, 0.02+(#page.children*0.04), 0), -- Shorthand positioning w/o a for-loop
texture = icon,
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local _tooltip = toolTip.construct("horizontal", iconImage, tooltip, positionToolTipOverride) -- Initialize tooltip instance
--button:mouseLeftPressed(callback) -- When button is clicked, perform callback action
-- When mouse hovers over button, display tooltip
iconImage:on("mouseFocused", function()
_tooltip.display()
end)
-- When mouse leaves from button, hide tooltip
iconImage:on("mouseUnfocused", function()
_tooltip.hide()
end)
end
return data
end
}

View File

@ -1,155 +0,0 @@
-- Copyright 2020- Teverse
-- This script constructs (or builds) the topbar controller
local globals = require("tevgit:workshop/library/globals.lua") -- globals; variables or instances that can be shared between files
local toolTip = require("tevgit:workshop/library/ui/components/toolTip.lua") -- UI component
return {
construct = function(idValue, titleIconValue, titleValue)
--[[
@Description
Constructor method that initializes the topbar instance.
@Params
String, idValue
String, titleIconValue
String, titleValue
@Returns
Instance, topbar
]]--
local data = {}
self = data -- Ease of use
self.id = idValue
self.title = titleValue
self.titleIcon = titleIconValue
self.keys = {} -- Where item keys are stored
local container = engine.construct("guiFrame", globals.workshop.interface, {
size = guiCoord(1, 0, 0.05, 0),
position = guiCoord(0, 0, 0, 0),
backgroundColour = globals.defaultColours.white,
})
engine.construct("guiImage", container, {
size = guiCoord(0, 28, 0, 28),
position = guiCoord(0.01, 0, 0.1, 0),
texture = titleIconValue,
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
handleEvents = false,
})
engine.construct("guiTextBox", container, {
size = guiCoord(0.5, 0, 0.1, 0),
position = guiCoord(0.04, 0, 0.05, 0),
text = titleValue,
textColour = globals.defaultColours.primary,
fontFile = "local:OpenSans-Bold.ttf",
fontSize = 30,
readOnly = true
})
engine.construct("guiTextBox", container, {
size = guiCoord(0.48, 0, 0.1, 0),
position = guiCoord(0.86, 0, 0.1, 0),
text = globals.user[2],
textColour = globals.defaultColours.primary,
fontSize = 25,
readOnly = true
})
local userIcon = engine.construct("guiFrame", container, {
size = guiCoord(0, 32, 0, 32),
position = guiCoord(0.82, 0, 0, 0),
backgroundColour = globals.defaultColours.primary,
borderRadius = 100
})
local statusIcon = engine.construct("guiFrame", container, {
size = guiCoord(0, 16, 0, 16),
position = guiCoord(0.836, 0, 0.5, 0),
backgroundColour = globals.defaultColours.green,
borderWidth = 2,
borderColour = globals.defaultColours.white,
borderAlpha = 1,
borderRadius = 32,
zIndex = 100
})
local undoButton = engine.construct("guiImage", container, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.92, 0, 0.2, 0),
texture = "fa:s-arrow-left",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local redoButton = engine.construct("guiImage", container, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.94, 0, 0.2, 0),
texture = "fa:s-arrow-right",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
local settingsButton = engine.construct("guiImage", container, {
size = guiCoord(0, 20, 0, 20),
position = guiCoord(0.97, 0, 0.2, 0),
texture = "fa:s-sliders-h",
imageColour = globals.defaultColours.primary,
backgroundColour = globals.defaultColours.white,
})
self.register = function(name, tooltip, page)
--[[
@Description
Register method that appends to the topbar instance.
@Params
String, name
String, tooltip
Instance, page
@Returns
Void, null, nil
]]--
table.insert(self.keys, {name})
local button = engine.construct("guiButton", container, {
size = guiCoord(0.056, 0, 0.9, 0),
position = guiCoord(0.2+(#self.keys*0.07), 0, 0.05, 0),
text = name,
textColour = globals.defaultColours.primary,
fontSize = 30,
align = enums.align.middle,
zIndex = 100
})
local _tooltip = toolTip.construct("vertical", button, tooltip) -- Initialize tooltip instance
button:mouseLeftPressed(function()
globals.sideBarPageActive.visible = (not globals.sideBarPageActive.visible) -- Unlist active page from view
if globals.sideBarPageActive == page then -- If the same page is clicked twice, unlist and replace with default page
globals.sideBarPageActive = globals.sideBarPageDefault
globals.sideBarPageDefault.visible = true
return -- Acts as a break
end
globals.sideBarPageActive = page
page.visible = (not page.visible)
end)
-- When mouse hovers over button, display tooltip
button:on("mouseFocused", function()
_tooltip.display()
end)
-- When mouse leaves from button, hide tooltip
button:on("mouseUnfocused", function()
_tooltip.hide()
end)
end
return data
end
}

View File

@ -1,55 +0,0 @@
-- Copyright 2020- Teverse
-- This script is responsible for creating the workshop interface
local globals = require("tevgit:workshop/library/globals.lua") -- globals; variables or instances that can be shared between files
local prompt = require("tevgit:workshop/library/ui/components/prompt.lua") -- UI Component
local topbarInterface = require("tevgit:workshop/library/ui/controllers/topbarInterface.lua") -- UI Controller
local sidebarInterface = require("tevgit:workshop/library/ui/controllers/sidebarInterface.lua") -- UI Controller
local topBar = topbarInterface.construct("horizontalNavbar", "fa:s-cloud", "Test Place 1") -- Create initial topbar instance
local sideBar = sidebarInterface.construct("verticalNavbar") -- Create initial sidebar instance
local defaultPage = sideBar.registerPage("Default") -- Register default page to sidebar instance
sideBar.registerIcon(defaultPage, "openFolderIcon", "fa:s-folder-open", "Open Folder", nil)
sideBar.registerIcon(defaultPage, "newFileIcon", "fa:s-file", "Create new file", nil)
sideBar.registerIcon(defaultPage, "uploadFileIcon", "fa:s-file-upload", "Upload current file", nil)
sideBar.registerIcon(defaultPage, "downloadFileIcon", "fa:s-file-download", "Download current file", nil)
sideBar.registerIcon(defaultPage, "importFileIcon", "fa:s-file-import", "Import a file", nil, -0.048, guiCoord(0.048, 0, 0, 0))
sideBar.registerIcon(defaultPage, "exportFileIcon", "fa:s-file-export", "Export current file", nil, 0.048, guiCoord(-0.048, 0, 0, 0))
defaultPage.visible = true -- Set default sidebar page to visible
globals.sideBarPageDefault = defaultPage -- Set default sidebar page to default
globals.sideBarPageActive = defaultPage -- Set default sidebar page as active
local designPage = sideBar.registerPage("Design") -- Design default page to sidebar instance
sideBar.registerIcon(designPage, "screenContainerIcon", "fa:s-tv", "Create a container instance", nil)
sideBar.registerIcon(designPage, "guiFrameIcon", "fa:s-square-full", "Create a guiFrame instance", nil)
sideBar.registerIcon(designPage, "guiTextBoxIcon", "fa:s-i-cursor", "Create a guiTextBox instance", nil)
sideBar.registerIcon(designPage, "guiImageIcon", "fa:s-image", "Create a guiImage instance", nil)
local modelPage = sideBar.registerPage("Model") -- Register model page to sidebar instance
sideBar.registerIcon(modelPage, "modelIcon", "fa:s-shapes", "Group instance(s) together", nil)
local insertPage = sideBar.registerPage("Insert") -- Register insert page to sidebar instance
sideBar.registerIcon(insertPage, "blockIcon", "fa:s-cube", "Create a cube instance", nil)
sideBar.registerIcon(insertPage, "circleIcon", "fa:s-circle", "Create a cylinder instance", nil)
--sideBar.registerIcon(designPage, "triangleIcon", "fa:s-i-cursor", nil, nil) -- Triangle / Wedge Icon doesn't exist
sideBar.registerIcon(insertPage, "scriptIcon", "fa:s-code", "Create a script instance", nil)
sideBar.registerIcon(insertPage, "lightIcon", "fa:s-lightbulb", "Create a light instance", nil)
local testPage = sideBar.registerPage("Test") -- Register test page to sidebar instance
sideBar.registerIcon(testPage, "consoleIcon", "fa:s-terminal", " Open console window", nil)
sideBar.registerIcon(testPage, "playIcon", "fa:s-play", "Play scene", nil)
sideBar.registerIcon(testPage, "serverIcon", "fa:s-server", "Configure server", nil)
sideBar.registerIcon(testPage, "fullScreenIcon", "fa:s-fullscreen", "Toggle full screen", nil)
-- Register topbar button (name labels) to topbar instance
topBar.register("Design", "Design your guis", designPage)
topBar.register("Model", "Model your scene", modelPage)
topBar.register("Insert", "Insert an instance to your scene", insertPage)
topBar.register("Test", "Test your scene", testPage)

View File

@ -1,27 +1,27 @@
-- Copyright 2020- Teverse
-- This script is required when workshop is loaded.
-- Copyright 2020 Teverse
-- This script is required when workshop is loaded,
-- and engine.workshop is passed to the function returned.
-- e.g. require('tevgit:workshop/main.lua')(engine.workshop)
local globals = require("tevgit:workshop/library/globals.lua") -- globals; variables or instances that can be shared between files
local function init(workshop)
local function beginLoad(workshop)
--[[
@Description
The initializer method that comes first when a new scene is open.
Teverse currently downloads tevgit files from the github repo when requested by the client.
This is quite slow, so there's a delay between EACH require when the user doesn't have a local tevgit.
@Params
Instance, workshop
To compensate for this, we'll quickly draw up a UI here BEFORE requiring anymore remote files.
We will load some files first in order to make a reload button for Development users.
--]]
@Returns
void, null, nil
]]--
-- Load this now, we need it to make the reload button
local shared = require("tevgit:workshop/controllers/shared.lua")
shared.workshop = workshop
shared.developerMode = not shared.workshop.hasLocalTevGit or shared.workshop:hasLocalTevGit()
globals.workshop = workshop -- Set workshop instance as a global
globals.user = engine:isAuthenticated() -- Set & Streamline user instance as a global
globals.developerMode = (not globals.workshop.hasLocalTevGit) or (globals.workshop:hasLocalTevGit()) -- Set developmode boolean as a global
local loadingScreen = engine.construct("guiFrame", workshop.interface, {
local loadingScreen;
do
loadingScreen = engine.construct("guiFrame", workshop.interface, {
size = guiCoord(1, 0, 1, 0),
backgroundColour = globals.defaultColours.background,
backgroundColour = colour:fromRGB(66, 66, 76),
zIndex = 1000
})
@ -33,61 +33,111 @@ local function init(workshop)
text = "Downloading the latest workshop...\nThis takes longer than a moment during beta."
})
-- Load stuff before initial load in
require("tevgit:workshop/library/ui/controllers/workshopInterface.lua")
if shared.developerMode then
local emergencyReload = engine.construct("guiTextBox", loadingScreen, {
text = "Emergency Reload",
size = guiCoord(0, 200, 0, 30),
position = guiCoord(0.5, -100, 1, -40),
backgroundColour = colour:fromRGB(44, 47, 51),
textColour = colour:fromRGB(255, 255, 255),
borderRadius = 3,
hoverCursor = "fa:s-hand-pointer",
align = enums.align.middle,
})
emergencyReload:mouseLeftPressed(function()
shared.workshop:reloadCreate()
end)
end
local spinner = engine.construct("guiImage", loadingScreen, {
size = guiCoord(0, 24, 0, 24),
position = guiCoord(1, -44, 1, -44),
texture = "fa:s-sync-alt",
backgroundAlpha = 0
})
local tween;
tween = engine.tween:begin(spinner, 1, {
rotation = math.rad(-360)
}, "inOutQuad", function ()
tween:reset()
tween:resume()
end)
end
-- Okay now we can load remote files whilst the user is looking at a loading screen.
shared.controllers.env = require("tevgit:workshop/controllers/environment/main.lua")
shared.controllers.history = require("tevgit:workshop/controllers/core/history.lua")
shared.controllers.clipboard = require("tevgit:workshop/controllers/core/clipboard.lua")
-- Create the Teverse interface
require("tevgit:workshop/controllers/ui/createInterface.lua")
-- Setup the 3D world
shared.controllers.env:setupEnvironment()
shared.controllers.env:createDefaultMap()
--shared.controllers.env:createPBRDebugSpheres()
-- Loading is no longer needed by this phase, remove if still valid
if loadingScreen then
loadingScreen:destroy()
loadingScreen = nil
end
local colourPicker = require("tevgit:workshop/controllers/ui/components/colourPicker.lua")
--print("Workshop Loaded. ", #engine.workspace.children) Lets not spam the console
end
return function( workshop )
--[[
@Description
The main method that comes when a new scene is opened.
local success, message = pcall(beginLoad, workshop)
@Params
Instance, workshop
@Returns
function, method
]]--
local success, message = pcall(init, workshop)
-- If initialize phase fails, prompt to the error screen
if (not success) then
if not success then
workshop.interface:destroyAllChildren()
local errorScreen = engine.construct("guiFrame", workshop.interface, {
size = guiCoord(1, 0, 1, 0),
backgroundColour = globals.defaultColours.background,
backgroundColour = colour:fromRGB(0, 0, 128),
zIndex = 10000
})
engine.construct("guiTextBox", errorScreen, {
size = guiCoord(0.8, 0, 0.8, 0),
position = guiCoord(0.1, 0, 0.1, 0),
backgroundColour = globals.defaultColours.background,
textColour = globals.defaultColours.red,
textColour = colour:fromRGB(255, 255, 0),
align = enums.align.topLeft,
text = "Error loading Workshop\nIf this isn't your fault, please take a screenshot and report this as a bug. \n\n" .. message .." \n\nPlease press 'ENTER' on your keyboard to restart Teverse.",
backgroundAlpha = 0,
text = "Error loading Workshop\nIf this isn't your fault, please take a screenshot and report this as a bug.\n\n" .. message,
wrap = true,
fontSize = 16,
fontFile = "tevurl:fonts/PxPlus_AmstradPC1512-2y.ttf"
})
-- Bind the "return" key on the keyboard as temporary fast-reload keybind
engine.input:on("keyPressed", function(keyboard)
if keyboard.key == enums.key["return"] then
workshop:reloadCreate()
end
end)
end
-- delay showing this button for 1.5 seconds
-- prevent spam reloads
-- Bind the "f12" key on the keyboard as fast-reload keybind if initialize phase is successful
engine.input:on("keyPressed", function(keyboard)
if keyboard.key == enums.key["f12"] then
wait(1.5)
engine.construct("guiTextBox", errorScreen, {
text = "RELOAD",
size = guiCoord(0, 80, 0, 30),
position = guiCoord(.5, -40, 1, -60),
backgroundColour = colour:fromRGB(255, 255, 0),
textColour = colour:fromRGB(0, 0, 128),
hoverCursor = "fa:s-hand-pointer",
align = enums.align.middle,
fontFile = "tevurl:fonts/PxPlus_AmstradPC1512-2y.ttf"
}):mouseLeftPressed(function()
workshop:reloadCreate()
end)
engine.input:on("keyPressed", function(inputObj)
if inputObj.key == enums.key["return"] then
workshop:reloadCreate()
end
end)
end
end