├── README.md └── src ├── Core ├── UIState.lua ├── assetLoader.lua ├── base.lua ├── drawCommands.lua ├── init.lua ├── mosaic.lua ├── param.lua ├── theme.lua └── util.lua ├── Themes └── Primitive │ ├── assets │ └── roboto.ttf │ └── init.lua ├── Widgets ├── Button.lua ├── CheckBox.lua ├── Image.lua ├── ImageButton.lua ├── Label.lua ├── Layout.lua ├── Menu.lua ├── MenuBar.lua ├── MenuItem.lua ├── ProgressBar.lua ├── RadioButton.lua ├── Scaler.lua ├── Slider.lua ├── Spinner.lua ├── Stepper.lua ├── TextEntry.lua ├── Tooltip.lua └── Window.lua └── init.lua /README.md: -------------------------------------------------------------------------------- 1 | # Lovely Imgui 2 | 3 | LovelyImgui is a WIP immediate-mode gui written in pure Lua. It attempts to be **simple**, **stupid** and **highly customizable**! 4 | 5 | Currently it doesn't support that many widgets but in the future (perhaps with support of contributors) it'll support many essential widgets and features! This limitation doesn't mean it's not fit for development - infact a [complete project](http://github.com/YoungNeer/brief) has been made with this GUI!! 6 | 7 | The following screenshot can perhaps give you an idea of the number and type of widgets LI currently supports! 8 | 9 |

10 |
11 | A demo of LovelyImgui 12 |

13 | 14 | 15 | Here's a snippet showing a basic imgui created with Lovely-Imgui:: 16 | 17 | ```lua 18 | imgui = require 'imgui' 19 | 20 | function love.draw() 21 | if imgui.button('Hello world') then 22 | imgui.label('You are clicking on the button!!') 23 | end 24 | imgui.draw() 25 | end 26 | ``` 27 | 28 | ## Features of LovelyImgui 29 | 30 | - **Bloat-Free**: Unlike Slab and SUIT, LI doesn't create a whole *lot* of tables or garbage! This gives LI a significant performance boost compared to forementioned libraries! 31 | - **Completely Customizable**: You can change the colors or change the whole theme! Themes in LI are more like LAF in Java in the sense that themes not just define the look but also the "behaviour" - a feature you can exploit to make tweening possible in your theme! 32 | - **Simple and Comfortable Layout System**: While still in progress, LI's layout system is based on rows and is very easy to understand! Ofcourse it comes with several limitations but for the simplest GUI the layout system can be very helpful! 33 | - **Responsiveness**: Instead of talking, I guess I'll show you some GIFs:- 34 |

35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | ## Documentation 43 | 44 | Documentation will be live very soon once the alpha version is released! Curious souls can study the [examples](https://github.com/YoungNeer/lovely-imgui/tree/examples) to learn how the library works! 45 | 46 | ## Contribution 47 | 48 | Making a GUI is easy! Making a reusable GUI is hard and by hard I mean *really hard*! There's currently a lot of stuff that I can't figure out (such as menu-bars, combo-boxes, windows, etc) so definitely I need some help. (And by help I mean "contribution" just so you don't get the wrong idea :laughing:) 49 | 50 |

51 | -------------------------------------------------------------------------------- /src/Core/UIState.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ImGUI needs window-dimensions, cursor-positon, etc 3 | So UIState stores all these information! 4 | ]] 5 | 6 | --Setting default values *just in case*!! 7 | 8 | local UIState={ 9 | dt, --delta-time 10 | mouseX=-100, 11 | mouseY=-100, 12 | keyChar, --for text input 13 | scrollDX, 14 | scrollDY, 15 | mouseUp, --meant to be used externally through an interface! 16 | mouseDown, --used internally 17 | hotItem, 18 | activeItem, 19 | lastActiveItem, 20 | winWidth, 21 | winHeight 22 | } 23 | 24 | return UIState -------------------------------------------------------------------------------- /src/Core/assetLoader.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Whole point -> to load all the assets at one go!! 3 | Here Assets refer to quads and PNG images! 4 | ]] 5 | 6 | local AssetLoader={} 7 | local imgCache={} 8 | local slicesCache={} 9 | 10 | local CORE_PATH = (...):match("^(.+)%.[^%.]+") 11 | local Mosaic=require(CORE_PATH..'.mosaic') 12 | 13 | local function isAFile(url) 14 | if love.filesystem.getInfo(url) then 15 | return love.filesystem.getInfo(url).type=="file" 16 | end 17 | end 18 | 19 | local function masterLoad(path,func) 20 | path=path:gsub('[.]','/') 21 | local assets={} 22 | local items=love.filesystem.getDirectoryItems(path) 23 | for i,item in ipairs(items) do 24 | if not isAFile(path..'/'..item) then 25 | goto continue 26 | end 27 | local len=item:len() 28 | if len>4 and item:sub(len-3)=='.png' then 29 | assets[item:sub(1,item:len()-4)]=func(path..'/'..item) 30 | end 31 | ::continue:: 32 | end 33 | return assets 34 | end 35 | 36 | function AssetLoader.loadImages(path) 37 | if not imgCache[path] then 38 | imgCache[path]=masterLoad(path,Mosaic.loadImage) 39 | end 40 | return imgCache[path] 41 | end 42 | 43 | function AssetLoader.loadSlices(path) 44 | if not slicesCache[path] then 45 | slicesCache[path]=masterLoad(path,Mosaic.loadSlices) 46 | end 47 | return slicesCache[path] 48 | end 49 | 50 | return AssetLoader -------------------------------------------------------------------------------- /src/Core/base.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This module defines the very basic aspects of imgui! 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | 10 | local uiState=require(LIB_PATH..'.Core.UIState') 11 | local theme=require(LIB_PATH..'.Core.theme') 12 | local util=require(LIB_PATH..'.Core.util') 13 | local drawCommands=require(LIB_PATH..'.Core.drawCommands') 14 | local Window=require(LIB_PATH..'.Widgets.Window') 15 | local Layout=require(LIB_PATH..'.Widgets.Layout') 16 | 17 | 18 | local imgui={ 19 | ['_genID']=0, 20 | ['idle']=false, --don't process any widget if idle! 21 | ['autoIdle']=true, --make imgui auto-idle for performance optimization 22 | ['overSmart']=true, --don't hover any widget when another widget is already active! 23 | ['pianoMode']=nil, --you'll know it when you see it 24 | ['stack']=stack, 25 | ['layout']=Layout, 26 | ['theme']=theme, 27 | ['uiState']=uiState, 28 | ['drawCommands']=drawCommands 29 | } 30 | 31 | function imgui.genID() 32 | imgui['_genID']=imgui['_genID']+1 33 | return imgui['_genID'] 34 | end 35 | 36 | --Performance Optimizations may still be buggy; so use this 37 | function imgui.noOptimize() 38 | imgui.setCanvas(nil) 39 | imgui.setAutoIdle(nil) 40 | end 41 | 42 | --Set imgui over-smart 43 | function imgui.setOverSmart(v) 44 | imgui.overSmart=v 45 | end 46 | 47 | --Refresh the canvas if there is any canvas 48 | function imgui.refresh() 49 | if not drawCommands.directDraw and drawCommands.autoRefresh then 50 | drawCommands.needsRefresh=true 51 | end 52 | end 53 | 54 | function imgui.setAutoIdle(auto) 55 | imgui.idle=auto 56 | imgui.autoIdle=auto 57 | end 58 | 59 | --Should imgui draw to canvas or directly! 60 | function imgui.setCanvas(canvas) 61 | drawCommands.directDraw=not canvas 62 | end 63 | 64 | --You'll know it when you see it!! 65 | function imgui.setPianoMode(mode) 66 | imgui.pianoMode=mode 67 | end 68 | 69 | function imgui.draw() 70 | imgui.drawCommands.draw() 71 | if imgui.autoIdle then imgui.idle=true end 72 | if uiState.mouseUp==1 then uiState.mouseUp=nil end 73 | if uiState.mouseUp==0 then uiState.mouseUp=1 end 74 | if uiState.mouseUp then uiState.mouseUp=nil end 75 | -- if uiState.mouseUp then print(uiState.mouseUp) end 76 | end 77 | 78 | ---At every frame we need to reset hotItem to nil 79 | local function imgui_prepare() 80 | imgui['_genID']=0 81 | -- uiState.keyChar=nil 82 | uiState.hotItem=nil 83 | if imgui.pianoMode then 84 | print('doing') 85 | uiState.activeItem=nil 86 | end 87 | Layout.reset() 88 | end 89 | 90 | function imgui.update(dt) 91 | if imgui.idle then return end 92 | imgui.drawCommands.clear() 93 | imgui_prepare() 94 | theme.update(dt) --> why? answered in theme.lua 95 | uiState.dt=dt 96 | end 97 | 98 | --should we support scankeys?? Who uses scan-keys anyway? 99 | function imgui.keypressed(key) 100 | uiState.keyDown=key 101 | if imgui.autoIdle then imgui.idle=false end 102 | imgui.refresh() 103 | end 104 | 105 | function imgui.keyreleased(key) 106 | uiState.keyDown=nil 107 | if imgui.autoIdle then imgui.idle=false end 108 | imgui.refresh() 109 | end 110 | 111 | 112 | function imgui.textinput(text) 113 | uiState.keyChar=text 114 | if imgui.autoIdle then imgui.idle=false end 115 | imgui.refresh() 116 | end 117 | 118 | function imgui.mousepressed(x,y,btn) 119 | if btn==1 then 120 | uiState.mouseDown=true 121 | if imgui.autoIdle then imgui.idle=false end 122 | imgui.refresh() 123 | end 124 | end 125 | 126 | function imgui.mousereleased(x,y,btn) 127 | if btn==1 then 128 | uiState.mouseUp=true 129 | uiState.mouseDown=false 130 | uiState.lastActiveItem=uiState.activeItem 131 | uiState.activeItem=nil 132 | if imgui.autoIdle then imgui.idle=false end 133 | imgui.refresh() 134 | end 135 | end 136 | 137 | function imgui.mousemoved(x,y) 138 | uiState.mouseX,uiState.mouseY=x,y 139 | if imgui.autoIdle then imgui.idle=false end 140 | imgui.refresh() 141 | end 142 | 143 | function imgui.wheelmoved(x,y) 144 | uiState.scrollDX=uiState.scrollDX+x 145 | uiState.scrollDY=uiState.scrollDY+y 146 | if imgui.autoIdle then imgui.idle=false end 147 | imgui.refresh() 148 | end 149 | 150 | --[INTERNAL]: An attempt to remove redundant logic 151 | function imgui.updateWidget(id,cond) 152 | if cond then 153 | if imgui.overSmart then 154 | if uiState.activeItem and uiState.activeItem~=id then return end 155 | end 156 | uiState.hotItem=id 157 | if uiState.mouseDown and not uiState.activeItem then 158 | uiState.activeItem=id 159 | end 160 | else 161 | if not imgui.pianoMode then 162 | if uiState.activeItem==id then uiState.activeItem=0 end 163 | end 164 | end 165 | end 166 | 167 | 168 | function imgui.resize(w,h) 169 | imgui.resetCanvas(w,h) 170 | if imgui.autoIdle then imgui.idle=false end 171 | imgui.refresh() 172 | end 173 | 174 | --There's also imgui.resize!! 175 | local setMode=love.window.setMode 176 | love.window.setMode=function(w,h,...) 177 | imgui.resetCanvas(w,h) 178 | setMode(w,h,...) 179 | end 180 | 181 | --We need to make a new canvas on resizing 182 | function imgui.resetCanvas(w,h) 183 | --We'll make a new canvas only for increase in window-size! 184 | if w>Window.stack[1].width or h>Window.stack[1].height then 185 | drawCommands.canvas:release() 186 | drawCommands.canvas=love.graphics.newCanvas(w,h) 187 | end 188 | Window.stack[1].width,Window.stack[1].height=w,h 189 | end 190 | 191 | return imgui -------------------------------------------------------------------------------- /src/Core/drawCommands.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | We don't need to draw at every frame! That'd be stupid 3 | So to make the library "bloat-free", imgui draws to a 4 | canvas which only updates when an input happens! 5 | Now using a canvas which may result in loss of quality 6 | so to turn off this feature simply say `imgui.setCanvas(false)` 7 | anywhere before `imgui.draw()`! 8 | To explicity refresh canvas say - `imgui.refresh()` 9 | ]] 10 | 11 | local CORE_PATH = (...):match("^(.+)%.[^%.]+") 12 | 13 | --Command Format: 14 | -- function() theme.drawButtonNormal(t,x,y,w,h) end 15 | local commands={} 16 | 17 | local drawCommands={ 18 | reverseTop=0, --needed for handling Z-index 19 | canvas=love.graphics.newCanvas(love.graphics.getDimensions()), 20 | directDraw=false, 21 | needsRefresh=true, --refresh canvas? 22 | autoRefresh=true, --automatically refresh canvas? 23 | commands=commands 24 | } 25 | 26 | local setCanvas=love.graphics.setCanvas 27 | 28 | drawCommands.draw=function() 29 | if drawCommands.directDraw or 30 | (drawCommands.autoRefresh and drawCommands.needsRefresh) then 31 | -- print('drawing') 32 | if drawCommands.needsRefresh and not drawCommands.directDraw then 33 | -- print('setting canvas') 34 | setCanvas(drawCommands.canvas) 35 | love.graphics.clear() 36 | end 37 | --Z-index is automatically handled before drawing! 38 | drawCommands.handleDepth() 39 | for i=1,#commands do 40 | commands[i]() 41 | end 42 | if drawCommands.needsRefresh and not drawCommands.directDraw then 43 | drawCommands.needsRefresh=false 44 | drawCommands.clear() --> TODO: Check if this is buggy 45 | setCanvas() 46 | end 47 | end 48 | if not drawCommands.directDraw then 49 | love.graphics.setColor(1,1,1) 50 | love.graphics.draw(drawCommands.canvas) 51 | end 52 | drawCommands.reverseTop=0 53 | end 54 | 55 | drawCommands.registerCommand=function (zindex,func) 56 | if not func then func,zindex=zindex,#commands+1 end 57 | 58 | if zindex==-1 then 59 | --We want this widget to have high depth! 60 | drawCommands.reverseTop=drawCommands.reverseTop-1 61 | commands[drawCommands.reverseTop]=func 62 | else 63 | table.insert(commands,zindex,func) 64 | end 65 | drawCommands.needsRefresh=true 66 | end 67 | 68 | --Move all the negative values to positive values! 69 | drawCommands.handleDepth=function() 70 | local i=-1 71 | while true do 72 | if not commands[i] then return end 73 | table.insert(commands,commands[i]) 74 | commands[i]=nil 75 | i=i-1 76 | end 77 | end 78 | 79 | drawCommands.clear=function() 80 | if #commands>100 then 81 | commands={} 82 | else 83 | for i=1,#commands do table.remove(commands,i) end 84 | end 85 | end 86 | 87 | return drawCommands -------------------------------------------------------------------------------- /src/Core/init.lua: -------------------------------------------------------------------------------- 1 | local CORE_PATH = (...) 2 | 3 | imgui=require(CORE_PATH..'.base') 4 | 5 | function imgui.init() 6 | love.keyboard.setKeyRepeat(true) 7 | end 8 | 9 | function imgui.isMouseReleased() 10 | return imgui.uiState.mouseUp 11 | end 12 | 13 | function imgui.getNextID() 14 | --this will get the ID of the next widget! 15 | return imgui['_genID']+1 16 | end 17 | 18 | return imgui -------------------------------------------------------------------------------- /src/Core/mosaic.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | [DISCLAIMER: Stolen from Luigi! Credit:Airstruck ] 3 | 4 | Mosaic's main job is to cut slices which are then 5 | used when rendering a widget which is done by it as well! 6 | This module is also responsible for maintaining a img-cache 7 | ]] 8 | 9 | local Mosaic={} 10 | 11 | local imgCache = {} 12 | local sliceCache = {} 13 | 14 | function Mosaic.loadImage (path) 15 | if not imgCache[path] then 16 | imgCache[path] = love.graphics.newImage(path) 17 | end 18 | return imgCache[path] 19 | end 20 | 21 | local makeSlice = love.graphics.newQuad 22 | local drawSlice=love.graphics.draw 23 | 24 | function Mosaic.loadSlices (path) 25 | local slices = sliceCache[path] 26 | 27 | if not slices then 28 | slices = {} 29 | sliceCache[path] = slices 30 | local image = Mosaic.loadImage(path) 31 | local iw, ih = image:getWidth(), image:getHeight() 32 | local w, h = math.floor(iw / 3), math.floor(ih / 3) 33 | 34 | slices.image = image 35 | slices.width = w 36 | slices.height = h 37 | 38 | slices.topLeft = makeSlice(0, 0, w, h, iw, ih) 39 | slices.topCenter = makeSlice(w, 0, w, h, iw, ih) 40 | slices.topRight = makeSlice(iw - w, 0, w, h, iw, ih) 41 | slices.middleLeft = makeSlice(0, h, w, h, iw, ih) 42 | slices.middleCenter = makeSlice(w, h, w, h, iw, ih) 43 | slices.middleRight = makeSlice(iw - w, h, w, h, iw, ih) 44 | slices.bottomLeft = makeSlice(0, ih - h, w, h, iw, ih) 45 | slices.bottomCenter = makeSlice(w, ih - h, w, h, iw, ih) 46 | slices.bottomRight = makeSlice(iw - w, ih - h, w, h, iw, ih) 47 | end 48 | return slices 49 | end 50 | 51 | function Mosaic.draw(img,slices,x,y,w,h) 52 | local sw, sh = slices.width, slices.height 53 | local xs = (w - sw * 2) / sw -- x scale 54 | local ys = (h - sh * 2) / sh -- y scale 55 | 56 | drawSlice(img,slices.middleCenter, x + sw, y + sh, 0, xs, ys) 57 | drawSlice(img,slices.topCenter, x + sw, y, 0, xs, 1) 58 | drawSlice(img,slices.bottomCenter, x + sw, y + h - sh, 0, xs, 1) 59 | drawSlice(img,slices.middleLeft, x, y + sh, 0, 1, ys) 60 | drawSlice(img,slices.middleRight, x + w - sw, y + sh, 0, 1, ys) 61 | drawSlice(img,slices.topLeft, x, y) 62 | drawSlice(img,slices.topRight, x + w - sw, y) 63 | drawSlice(img,slices.bottomLeft, x, y + h - sh) 64 | drawSlice(img,slices.bottomRight, x + w - sw, y + h - sh) 65 | end 66 | 67 | return Mosaic -------------------------------------------------------------------------------- /src/Core/param.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Since Magic-Numbers are bad we'll take care of them here! 3 | ]] 4 | 5 | local param={} 6 | 7 | function param.getStepperHeight(w) return w*.25 end 8 | 9 | function param.getStepperLeftButton(stepper,x,y,w,h) 10 | return x,y,w*.2,h 11 | end 12 | 13 | function param.getStepperRightButton(stepper,x,y,w,h) 14 | return x+w-w*.2,y,w*.2,h 15 | end 16 | 17 | return param -------------------------------------------------------------------------------- /src/Core/theme.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | How should a button be rendered when it's hovered, etc? 3 | All of this done by the (current) theme! 4 | ]] 5 | 6 | local LIB_PATH=(...): 7 | match("^(.+)%.[^%.]+"): 8 | match("^(.+)%.[^%.]+") 9 | 10 | local theme={ 11 | current, 12 | list={} 13 | } 14 | 15 | theme.list['default']=require(LIB_PATH..'.Themes.Primitive') 16 | 17 | --[[ 18 | Every widget will have an outline that 19 | depending on theme may or may not get drawn! 20 | ]] 21 | local funcs={ 22 | 'update','getFontSize','drawButtonNormal','drawButtonHover', 23 | 'drawButtonPressed','drawCheckBoxNormal', 24 | 'drawCheckBoxHover','drawCheckBoxPressed','drawRadioButtonNormal', 25 | 'drawRadioButtonHover','drawRadioButtonPressed','drawScalerNormal', 26 | 'drawScalerHover','drawScalerPressed', 27 | 'drawLabel','drawTooltip','drawProgressBar','drawMenuBar','drawMenuNormal', 28 | 'drawMenuHover','drawSliderNormal','drawSliderHover','drawSliderPressed', 29 | 'drawTextEntryNormal','drawTextEntryHover','drawTextEntryPressed', 30 | 'drawStepperNormal','drawStepperHover','drawStepperPressed', 31 | 'drawOutline', 32 | } 33 | 34 | theme.set=function(themeName) 35 | theme.current=theme.list[themeName] 36 | --So rendering the theme will simply render the current theme! 37 | for _,func in ipairs(funcs) do 38 | theme[func]=theme.current[func] 39 | end 40 | end 41 | 42 | theme.set('default') 43 | 44 | --[[ 45 | Why theme.update(dt)??? 46 | You know that themes not just affect appearance but also performance! 47 | And themes can just change the "look" but also the "feel" which 48 | makes them so similar to Java's LAF! With update, one can do tweening 49 | for a widget transition! (button-hover,etc!) 50 | 51 | Why onActivate and onDeactivate? 52 | So that themes can turn on/off idle-mode, piano-mode and canvas-rendering 53 | (eg. canvas-rendering is on by default and is good for performance but is 54 | sometimes bad for appearance hence a theme may disable it and enable it 55 | when it is switched!!) 56 | ]] 57 | 58 | return theme -------------------------------------------------------------------------------- /src/Core/util.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | We need some utility functions such as pointInRect etc! 3 | So that's the whole purpose of this util.lua! 4 | ]] 5 | 6 | local util={} 7 | local CORE_PATH=(...):match("^(.+)%.[^%.]+") 8 | local LIB_PATH=CORE_PATH:match("^(.+)%.[^%.]+") 9 | local uiState=require(CORE_PATH..'.UIState') 10 | local param=require(CORE_PATH..'.param') 11 | local mosaic=require(CORE_PATH..'.mosaic') 12 | local theme=require(CORE_PATH..'.theme') 13 | local Layout=require(LIB_PATH..'.Widgets.Layout') 14 | local Window=require(LIB_PATH..'.Widgets.Window') 15 | 16 | function util.warp(value,min,max) 17 | return value>max and min or (value x,y calc by Layout 84 | #3: img => x,y calc by Layout, w,h=image_size 85 | img can be an Image or a table! 86 | ]] 87 | function util.getImageParams(img,x,y,w,h) 88 | local color,r 89 | img=type(img)=='string' and mosaic.loadImage(img) or img 90 | 91 | if type(img)=='table' then 92 | color=img.color r=img.rotation img=img.image 93 | end 94 | 95 | 96 | if y and not w then 97 | --User gave us width and height but not x,y (#2) 98 | w,h=x,y x,y=nil 99 | elseif not x then 100 | --Calculate width and height (#3) 101 | w,h=img:getDimensions() 102 | end 103 | 104 | w,h=getFlexibleSize(w,h) 105 | 106 | if not x then 107 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 108 | else 109 | x,y=getXFromAlign(x,w),getYFromAlign(y,h) 110 | --Images already have their origin centered in draw function 111 | end 112 | 113 | return img,color,r,x,y,w,h 114 | end 115 | 116 | --[[ 117 | ---Overloads for imgui.checkBox--- 118 | #1: isChecked,text,x,y,w,h 119 | #2: isChecked,x,y,w,h => text=nil 120 | #3: isChecked,text,w,h => x,y calc by Layout 121 | #4: isChecked,w,h => text=nil, x,y calc by Layout 122 | #5: isChecked => x,y calc by Layout, w,h=16,16 123 | ]] 124 | 125 | function util.getCheckBoxParams(isChecked,text,x,y,w,h) 126 | if type(text)=='number' then --#2 or #4 127 | x,y,w,h,text=text,x,y,w 128 | end 129 | if y and not w then --#3 or #4 130 | w,h=x,y 131 | x,y=nil 132 | elseif not x then --#5 133 | w,h=16,16 134 | end 135 | 136 | w,h=getFlexibleSize(w,h) 137 | 138 | if not x then 139 | x,y=Layout.getPosition(w+theme.getFontSize('checkbox',text)+10,h) --Let layout do its job!! 140 | else 141 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 142 | --For Client: CheckBoxes have origin at the center! 143 | end 144 | 145 | return isChecked,text,x,y,w,h 146 | end 147 | 148 | util.getRadioButtonParams=util.getCheckBoxParams 149 | 150 | --[[ 151 | ---Overloads for imgui.label--- 152 | #1: text,x,y 153 | #2: text => x,y calc by Layout 154 | ]] 155 | 156 | function util.getLabelParams(text,x,y) 157 | local w,h 158 | if type(text)=='string' then 159 | w,h=theme.getFontSize('label',text) 160 | else 161 | w,h=text.font:getWidth(text.text),text.font:getHeight() 162 | end 163 | if not x then --#2 164 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 165 | else --#1 166 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 167 | --For Client: Labels have origin at the center! 168 | end 169 | return x,y 170 | end 171 | 172 | --[[ 173 | ---Overloads for imgui.tooltip--- 174 | #1: text,x,y 175 | #2: text,x => y default to mouseY 176 | #3: text => x,y default to mouseX,mouseY 177 | ]] 178 | 179 | function util.getTooltipParams(text,x,y) 180 | local w,h 181 | if type(text)=='string' then 182 | w,h=theme.getFontSize('tooltip',text) 183 | else 184 | w,h=text.font:getWidth(text.text),text.font:getHeight() 185 | end 186 | x=x and (getXFromAlign(x,w)-w/2) or uiState.mouseX 187 | y=y and (getYFromAlign(y,h)-h/2) or (uiState.mouseY+h) 188 | --For Client: Tooltips have origin at the center! 189 | return x,y 190 | end 191 | 192 | 193 | --[[ 194 | ---Overloads for imgui.button--- 195 | #1: text,x,y,w,h 196 | #2: text,w,h => x,y calc by Layout 197 | #3: text => x,y calc by Layout, w,h from Label 198 | ]] 199 | 200 | function util.getButtonParams(text,x,y,w,h) 201 | if y and not w then 202 | --User gave us width and height but not x,y (#2) 203 | w,h=x,y x,y=nil 204 | elseif not w then 205 | --Get width and height from text (#3) 206 | w,h=theme.getFontSize('button',text) 207 | end 208 | w,h=getFlexibleSize(w,h) 209 | if not x then 210 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 211 | else 212 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 213 | --(#1) For Client: Buttons have origin at the center! 214 | end 215 | return text,x,y,w,h 216 | end 217 | 218 | 219 | --[[ 220 | ---Overloads for imgui.imageButton--- 221 | #1: image,x,y,w,h 222 | #2: image,w,h => x,y calc by Layout 223 | #3: image => x,y calc by Layout, w,h from image! 224 | ]] 225 | 226 | function util.getImageButtonParams(img,x,y,w,h) 227 | if y and not w then 228 | --User gave us width and height but not x,y (#2) 229 | w,h=x,y x,y=nil 230 | elseif not w then 231 | --Get width and height from text (#3) 232 | w,h=img.image:getDimensions() 233 | end 234 | w,h=getFlexibleSize(w,h) 235 | if not x then 236 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 237 | else 238 | x,y=getXFromAlign(x,w),getYFromAlign(y,h) 239 | --Images already have their origin centered in draw function 240 | end 241 | return x,y,w,h 242 | end 243 | 244 | --[[ 245 | ---Overloads for imgui.progressBar--- 246 | #1: progress,x,y,w,h 247 | #2: progress,w,h => x,y calc by Layout 248 | #3: progress,w => x,y calc by Layout, h default to 25 249 | ]] 250 | 251 | function util.getProgressBarParams(progress,x,y,w,h) 252 | if y and not w then 253 | --User gave us width and height but not x,y (#2) 254 | w,h=x,y x,y=nil 255 | elseif x and not y then 256 | --Get width and set height to default (#3) 257 | w=x h=25 x=nil 258 | end 259 | w,h=getFlexibleSize(w,h) 260 | if not x then 261 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 262 | else 263 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 264 | --For Client: Progress Bars have origins in the center 265 | end 266 | return x,y,w,h 267 | end 268 | 269 | --[[ 270 | ---Overloads for imgui.scaler--- 271 | #1: scaler,x,y,w,h 272 | #2: scaler,w,h => x,y calc by Layout 273 | #3: scaler,w/h => x,y calc by Layout, h/w -> auto_calc 274 | ]] 275 | 276 | function util.getScalerParams(scaler,x,y,w,h) 277 | if y and not w then 278 | --User gave us width and height but not x,y (#2) 279 | w,h=x,y x,y=nil 280 | elseif x and not y then 281 | --Calculate width/height from scaler (#3) 282 | if scaler.orientation=='t-b' or scaler.orientation=='b-t' then w,h=25,x 283 | else w,h=x,25 end 284 | x=nil 285 | end 286 | w,h=getFlexibleSize(w,h) 287 | if not x then 288 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 289 | else 290 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 291 | end 292 | 293 | scaler.min=scaler.min or 0 294 | scaler.max=scaler.max or 1 295 | scaler.value=scaler.value or scaler.min 296 | 297 | return x,y,w,h 298 | end 299 | 300 | --[[ 301 | ---Overloads for imgui.slider--- 302 | #1: slider,x,y,w,h 303 | #2: slider,x,y,w/h => h/w -> auto_calc 304 | #2: slider,w,h => x,y calc by Layout 305 | #3: slider,w/h => x,y calc by Layout, h/w -> auto_calc 306 | ]] 307 | 308 | local THUMB_SIZE=25 --DRY CODING!!! For now I guess it's alright 309 | 310 | function util.getSliderParams(slider,x,y,w,h) 311 | if y and not w then 312 | --User gave us width and height but not x,y (#2) 313 | w,h=x,y x,y=nil 314 | elseif w and not h then 315 | --Calculate width/height automatically (#2) 316 | if slider.vertical then w,h=25,w 317 | else h=25 end 318 | elseif x and not y then 319 | --Calculate width/height from scaler (#3) 320 | if slider.vertical then w,h=25,x 321 | else w,h=x,25 end 322 | x=nil 323 | end 324 | w,h=getFlexibleSize(w,h) 325 | if not x then 326 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 327 | else 328 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 329 | end 330 | 331 | slider.min=slider.min or 0 332 | slider.max=slider.max or 1 333 | slider.value=slider.value or slider.min 334 | 335 | --Normalize the value of the slider! 336 | local fraction = (slider.value - slider.min) / (slider.max - slider.min) 337 | local sx,sy,sw,sh=x,y,w,h 338 | if slider.vertical then 339 | sy=util.constrain(y+h*fraction-THUMB_SIZE/2,y,y+h-THUMB_SIZE) sh=THUMB_SIZE 340 | else 341 | sx=util.constrain(x+w*fraction-THUMB_SIZE/2,x,x+w-THUMB_SIZE) sw=THUMB_SIZE 342 | end 343 | 344 | return fraction,x,y,w,h,sx,sy,sw,sh 345 | end 346 | 347 | --[[ 348 | ---Overloads for imgui.textEntry--- 349 | #1: textEntry,x,y,w,h=30 350 | #2: textEntry,w,h=30 => x,y calc by Layout 351 | ]] 352 | 353 | function util.getTextEntryParams(textEntry,x,y,w,h) 354 | if x and not w then 355 | --User gave us width and height but not x,y (#2) 356 | w,h=x,y x,y=nil 357 | end 358 | w,h=getFlexibleSize(w,h or 30) 359 | if not x then 360 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 361 | else 362 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 363 | end 364 | textEntry.text=textEntry.text or "" 365 | textEntry._cursor=textEntry._cursor or 0 366 | return x,y,w,h 367 | end 368 | 369 | 370 | --[[ 371 | ---Overloads for imgui.stepper--- 372 | #1: stepper,x,y,w,h=30 373 | #2: stepper,w,h=30 => x,y calc by Layout 374 | ]] 375 | 376 | function util.getStepperParams(stepper,x,y,w,h) 377 | if x and not w then 378 | --User gave us width and height but not x,y (#2) 379 | w,h=x,y x,y=nil 380 | end 381 | w,h=getFlexibleSize(w,h or param.getStepperHeight(w)) 382 | if not x then 383 | x,y=Layout.getPosition(w,h) --Let layout do its job!! 384 | else 385 | x,y=getXFromAlign(x,w)-w/2,getYFromAlign(y,h)-h/2 386 | end 387 | stepper.list=stepper.list or {""} 388 | stepper.active=stepper.active or 1 389 | return x,y,w,h 390 | end 391 | 392 | return util -------------------------------------------------------------------------------- /src/Themes/Primitive/assets/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/besnoi/lovely-imgui/e6152ce46014c1e3a619da30439179c6719cdd6a/src/Themes/Primitive/assets/roboto.ttf -------------------------------------------------------------------------------- /src/Themes/Primitive/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | The thing about primitive theme is that it's more efficient! 3 | ]] 4 | 5 | local ASSETS_PATH=(...):gsub('[.]','/')..'/assets/' 6 | 7 | local LIB_PATH=(...): 8 | match("^(.+)%.[^%.]+"): 9 | match("^(.+)%.[^%.]+") 10 | 11 | local param=require(LIB_PATH..'.Core.param') 12 | 13 | local theme={} 14 | local lg,ef=love.graphics,function() end 15 | 16 | theme.update=ef 17 | 18 | local nc,hc,ac={.5,.5,.5},{.55,.55,.55},{.45,.45,.45} 19 | 20 | --For Primitive theme, all widgets will have same color palette! 21 | theme.normalColor=nc 22 | theme.hotColor=hc 23 | theme.activeColor=ac 24 | 25 | -- For Primitive Theme, all widgets will have same font but you can change this if you like! 26 | theme.font=lg.newFont(ASSETS_PATH..'roboto.ttf',15) 27 | theme.arrowFont=lg.newFont(ASSETS_PATH..'roboto.ttf',20) --for arrows only 28 | 29 | --[[ 30 | Widgets can have different fonts but as a rule they must have 31 | the same font across different states (hovered,clicked,etc) 32 | getFontSize takes in the type of the widget and the text 33 | and returns the minimum-size that is required by that text! 34 | [Themes can be malice and return a greater or lower size!] 35 | ]] 36 | 37 | function theme.getFontSize(widget,text) 38 | local w,h=theme.font:getWidth(text),theme.font:getHeight() 39 | if widget=='button' then 40 | w,h=w+50,h+30 41 | end 42 | return w,h 43 | end 44 | 45 | function theme.drawOutline(widget,x,y,w,h) 46 | if widget=='button' then return end 47 | lg.setColor(0,.1,.1) 48 | lg.rectangle('line',x,y,w,h) 49 | end 50 | 51 | ---------------------HELPER FUNCTIONS----------------------- 52 | 53 | local function constrain(value,min,max) 54 | return math.min(max,math.max(value,min)) 55 | end 56 | 57 | local function drawButtonText(text,x,y,w,h) 58 | lg.setColor(.8,.8,.8) 59 | lg.setFont(theme.font) 60 | lg.printf(text,x,y+(h-theme.font:getHeight())/2,w,'center') 61 | end 62 | 63 | local function drawTick(x,y,w,h) 64 | lg.setLineStyle('smooth') 65 | lg.setLineWidth(w/16) 66 | lg.setLineJoin("bevel") 67 | lg.line(x+h*.2,y+h*.6, x+h*.45,y+h*.75, x+h*.8,y+h*.2) 68 | lg.setLineWidth(1) 69 | end 70 | 71 | local function drawCheckBoxText(text,x,y,h) 72 | lg.setColor(.2,.2,.2) 73 | lg.setFont(theme.font) 74 | lg.print(text,x,y+(h-theme.font:getHeight())/2) 75 | end 76 | 77 | local drawRadioButtonText=drawCheckBoxText 78 | 79 | ------------------------STEPPER------------------------ 80 | 81 | function theme.drawStepperNormal(stepper,x,y,w,h) 82 | local lx,ly,lw,lh=param.getStepperLeftButton(stepper,x,y,w,h) 83 | local rx,ry,rw,rh=param.getStepperRightButton(stepper,x,y,w,h) 84 | ly=ly-2 ry=ry-2 85 | lg.setColor(nc[1]-.05,nc[2]-.05,nc[3]-.05) 86 | lg.rectangle('fill',lx,ly,lw,lh+6,4,4) 87 | lg.rectangle('fill',rx,ry,rw,rh+6,4,4) 88 | lg.setColor(nc[1]+.03,nc[2]+.03,nc[3]+.03) 89 | lg.rectangle('fill',x,y,w,h,4,4) 90 | lg.setColor(unpack(nc)) 91 | lg.rectangle('fill',lx,ly,lw,lh+2,4,4) 92 | lg.rectangle('fill',rx,ry,rw,rh+2,4,4) 93 | lg.setColor(.2,.2,.2) 94 | lg.setColor(.8,.8,.8) 95 | lg.setFont(theme.font) 96 | lg.printf(stepper.list[stepper.active],x,y+(h-theme.font:getHeight())/2,w,'center') 97 | lg.setColor(.7,.7,.7) 98 | lg.setFont(theme.arrowFont) 99 | lg.printf('>',rx,ry+(rh-theme.font:getHeight())/2,rw,'center') 100 | lg.printf('<',lx,ly+(lh-theme.font:getHeight())/2,lw,'center') 101 | end 102 | 103 | function theme.drawStepperHover(stepper,left_hovered,x,y,w,h) 104 | local lx,ly,lw,lh=param.getStepperLeftButton(stepper,x,y,w,h) 105 | local rx,ry,rw,rh=param.getStepperRightButton(stepper,x,y,w,h) 106 | ly=ly-2 ry=ry-2 107 | lg.setColor(nc[1]-.05,nc[2]-.05,nc[3]-.05) 108 | lg.rectangle('fill',lx,ly,lw,lh+6,4,4) 109 | lg.rectangle('fill',rx,ry,rw,rh+6,4,4) 110 | lg.setColor(nc[1]+.03,nc[2]+.03,nc[3]+.03) 111 | lg.rectangle('fill',x,y,w,h,4,4) 112 | lg.setColor(unpack(nc)) 113 | if left_hovered then lg.setColor(unpack(hc)) end 114 | lg.rectangle('fill',lx,ly,lw,lh+2,4,4) 115 | lg.setColor(unpack(nc)) 116 | if not left_hovered then lg.setColor(unpack(hc)) end 117 | lg.rectangle('fill',rx,ry,rw,rh+2,4,4) 118 | lg.setColor(.2,.2,.2) 119 | lg.setColor(.8,.8,.8) 120 | lg.setFont(theme.font) 121 | lg.printf(stepper.list[stepper.active],x,y+(h-theme.font:getHeight())/2,w,'center') 122 | lg.setColor(.7,.7,.7) 123 | lg.setFont(theme.arrowFont) 124 | lg.printf('>',rx,ry+(rh-theme.font:getHeight())/2,rw,'center') 125 | lg.printf('<',lx,ly+(lh-theme.font:getHeight())/2,lw,'center') 126 | end 127 | 128 | function theme.drawStepperPressed(stepper,left_pressed,x,y,w,h) 129 | local lx,ly,lw,lh=param.getStepperLeftButton(stepper,x,y,w,h) 130 | local rx,ry,rw,rh=param.getStepperRightButton(stepper,x,y,w,h) 131 | ly=ly-2 ry=ry-2 132 | lg.setColor(nc[1]-.05,nc[2]-.05,nc[3]-.05) 133 | lg.rectangle('fill',lx,ly,lw,lh+6,4,4) 134 | lg.rectangle('fill',rx,ry,rw,rh+6,4,4) 135 | lg.setColor(nc[1]+.03,nc[2]+.03,nc[3]+.03) 136 | lg.rectangle('fill',x,y,w,h,4,4) 137 | lg.setColor(unpack(nc)) 138 | if left_pressed then lg.setColor(unpack(ac)) end 139 | lg.rectangle('fill',lx,ly,lw,lh+2,4,4) 140 | lg.setColor(unpack(nc)) 141 | if not left_pressed then lg.setColor(unpack(ac)) end 142 | lg.rectangle('fill',rx,ry,rw,rh+2,4,4) 143 | lg.setColor(.2,.2,.2) 144 | lg.setColor(.8,.8,.8) 145 | lg.setFont(theme.font) 146 | lg.printf(stepper.list[stepper.active],x,y+(h-theme.font:getHeight())/2,w,'center') 147 | lg.setColor(.7,.7,.7) 148 | lg.setFont(theme.arrowFont) 149 | lg.printf('>',rx,ry+(rh-theme.font:getHeight())/2,rw,'center') 150 | lg.printf('<',lx,ly+(lh-theme.font:getHeight())/2,lw,'center') 151 | end 152 | 153 | -----------------------TEXT ENTRY---------------------- 154 | 155 | function theme.drawTextEntryNormal(text,offset,cursor,x,y,w,h) 156 | lg.setColor(unpack(nc)) 157 | lg.rectangle('fill',x,y,w,h,4,4) 158 | lg.setFont(theme.font) 159 | lg.setColor(1,1,1) 160 | lg.setScissor(x,y,w,h) 161 | lg.setColor(.8,.8,.8) 162 | lg.print(text,x+3,y+(h-theme.font:getHeight())/2) 163 | lg.setScissor() 164 | end 165 | 166 | function theme.drawTextEntryHover(text,offset,cursor,x,y,w,h) 167 | lg.setColor(unpack(hc)) 168 | lg.rectangle('fill',x,y,w,h,4,4) 169 | lg.setFont(theme.font) 170 | lg.setColor(1,1,1) 171 | lg.setScissor(x,y,w,h) 172 | lg.setColor(.8,.8,.8) 173 | lg.print(text,x+3,y+(h-theme.font:getHeight())/2) 174 | lg.setScissor() 175 | end 176 | 177 | function theme.drawTextEntryPressed(text,offset,cursor,x,y,w,h) 178 | lg.setColor(unpack(ac)) 179 | lg.rectangle('fill',x,y,w,h,2,2) 180 | lg.setFont(theme.font) 181 | lg.setColor(1,1,1) 182 | local left=text:sub(1,cursor) 183 | local fontWidth=math.ceil((theme.font:getWidth(left)-w)/text:len()) 184 | local right=theme.font:getWidth(left)>w and text:sub(fontWidth,cursor) or text 185 | lg.setScissor(x,y,w,h) 186 | if love.timer.getTime()%1.5<=1 then 187 | lg.print('|',x+3+theme.font:getWidth(left),y+(h-theme.font:getHeight())/2) 188 | end 189 | lg.setColor(.8,.8,.8) 190 | lg.print(right,x+3,y+(h-theme.font:getHeight())/2) 191 | lg.setScissor() 192 | end 193 | 194 | -------------------------LABEL------------------------- 195 | 196 | function theme.drawLabel(text,x,y) 197 | lg.setColor(.8,.8,.8) 198 | lg.setFont(theme.font) 199 | lg.print(text,x,y) 200 | end 201 | 202 | -------------------------TOOLTIP------------------------- 203 | 204 | function theme.drawTooltip(text,x,y) 205 | lg.setColor(0,0,0) 206 | lg.rectangle('fill',x-4,y-2,theme.font:getWidth(text)+8,theme.font:getHeight()+2) 207 | lg.setColor(.8,.8,.8) 208 | lg.setFont(theme.font) 209 | lg.print(text,x,y) 210 | end 211 | 212 | --TODO Ask GFG if I can write a detailed tutorial on ImGUI! 213 | 214 | -----------------------MENU BAR------------------------ 215 | 216 | function theme.drawMenuBar(x,y,w,h) 217 | lg.setColor(unpack(nc)) 218 | lg.rectangle('fill',x,y,w,h+4) 219 | end 220 | 221 | -------------------------MENUS------------------------- 222 | 223 | function theme.drawMenuNormal(text,x,y) 224 | lg.setColor(.8,.8,.8) 225 | lg.setFont(theme.font) 226 | lg.print(text,x,y+2) 227 | end 228 | 229 | function theme.drawMenuHover(text,x,y,w,h) 230 | lg.setColor(unpack(hc)) 231 | lg.rectangle('fill',x-5,y,w+1,h+4) 232 | lg.setColor(.8,.8,.8) 233 | lg.setFont(theme.font) 234 | lg.print(text,x,y+2) 235 | end 236 | 237 | ----------------------PROGRESS BAR---------------------- 238 | 239 | function theme.drawProgressBar(text,value,x,y,w,h) 240 | lg.setColor(unpack(nc)) 241 | lg.rectangle('fill', x,y,w,h,2,2) 242 | w = w * value 243 | lg.setColor(.3,.3,.5) 244 | if value>0 then 245 | lg.rectangle('fill',x,y,w,h,2,2) 246 | end 247 | end 248 | 249 | ----------------------SCALER WIDGET--------------------- 250 | 251 | function theme.drawScalerNormal(fraction,orientation,x,y,w,h) 252 | lg.setColor(unpack(nc)) 253 | lg.rectangle('fill', x,y,w,h,2,2) 254 | 255 | if orientation=='t-b' or orientation=='b-t' then 256 | if orientation=='b-t' then y=y+h*(1-fraction) end 257 | h = h * fraction 258 | else 259 | if orientation=='l-r' then x=x+w*(1-fraction) end 260 | w = w * fraction 261 | end 262 | 263 | lg.setColor(nc[1]-.1,nc[2]-.1,nc[3]-.1) 264 | if fraction>0 then 265 | lg.rectangle('fill',x,y,w,h,2,2) 266 | end 267 | end 268 | 269 | function theme.drawScalerHover(fraction,orientation,x,y,w,h) 270 | lg.setColor(unpack(hc)) 271 | lg.rectangle('fill', x,y,w,h,2,2) 272 | 273 | if orientation=='t-b' or orientation=='b-t' then 274 | if orientation=='b-t' then y=y+h*(1-fraction) end 275 | h = h * fraction 276 | else 277 | if orientation=='l-r' then x=x+w*(1-fraction) end 278 | w = w * fraction 279 | end 280 | 281 | lg.setColor(hc[1]-.1,hc[2]-.1,hc[3]-.1) 282 | if fraction>0 then 283 | lg.rectangle('fill',x,y,w,h,2,2) 284 | end 285 | end 286 | 287 | function theme.drawScalerPressed(fraction,orientation,x,y,w,h) 288 | x=x+1 y=y+1 289 | lg.setColor(unpack(ac)) 290 | lg.rectangle('fill', x,y,w,h,2,2) 291 | 292 | if orientation=='t-b' or orientation=='b-t' then 293 | if orientation=='b-t' then y=y+h*(1-fraction) end 294 | h = h * fraction 295 | else 296 | if orientation=='l-r' then x=x+w*(1-fraction) end 297 | w = w * fraction 298 | end 299 | 300 | lg.setColor(ac[1]-.1,ac[2]-.1,ac[3]-.1) 301 | if fraction>0 then 302 | lg.rectangle('fill',x,y,w,h,2,2) 303 | end 304 | end 305 | 306 | local THUMB_SIZE=25 --> some DRY coding! Doesn't matter much! 307 | 308 | ----------------------SLIDER WIDGET--------------------- 309 | 310 | function theme.drawSliderNormal(fraction,vertical,x,y,w,h) 311 | lg.setColor(unpack(nc)) 312 | lg.rectangle('line', x,y,w,h,2,2) 313 | 314 | if vertical then 315 | y=constrain(y+h*fraction-THUMB_SIZE/2,y,y+h-THUMB_SIZE) 316 | h=THUMB_SIZE 317 | else 318 | x=constrain(x+w*fraction-THUMB_SIZE/2,x,x+w-THUMB_SIZE) 319 | w=THUMB_SIZE 320 | end 321 | 322 | lg.setColor(nc[1]-.1,nc[2]-.1,nc[3]-.1) 323 | lg.rectangle('fill',x,y,w,h,2,2) 324 | end 325 | 326 | function theme.drawSliderHover(fraction,vertical,x,y,w,h) 327 | lg.setColor(unpack(hc)) 328 | lg.rectangle('line', x,y,w,h,2,2) 329 | 330 | if vertical then 331 | y=constrain(y+h*fraction-THUMB_SIZE/2,y,y+h-THUMB_SIZE) 332 | h=THUMB_SIZE 333 | else 334 | x=constrain(x+w*fraction-THUMB_SIZE/2,x,x+w-THUMB_SIZE) 335 | w=THUMB_SIZE 336 | end 337 | 338 | lg.setColor(hc[1]-.1,hc[2]-.1,hc[3]-.1) 339 | lg.rectangle('fill',x,y,w,h,2,2) 340 | end 341 | 342 | function theme.drawSliderPressed(fraction,vertical,x,y,w,h) 343 | -- x=x+1 y=y+1 344 | lg.setColor(unpack(ac)) 345 | lg.rectangle('line', x,y,w,h,2,2) 346 | 347 | if vertical then 348 | y=constrain(y+h*fraction-THUMB_SIZE/2,y,y+h-THUMB_SIZE) 349 | h=THUMB_SIZE 350 | else 351 | x=constrain(x+w*fraction-THUMB_SIZE/2,x,x+w-THUMB_SIZE) 352 | w=THUMB_SIZE 353 | end 354 | 355 | lg.setColor(ac[1]-.1,ac[2]-.1,ac[3]-.1) 356 | lg.rectangle('fill',x,y,w,h,2,2) 357 | end 358 | 359 | -------------------RADIOBUTTON WIDGET---------------------- 360 | 361 | function theme.drawRadioButtonNormal(isChecked,text,x,y,w,h) 362 | lg.setColor(nc[1]-.15,nc[2]-.15,nc[3]-.15) 363 | lg.setLineWidth(3) 364 | lg.ellipse('line',x+w/2,y+h/2,w/2,h/2,40) 365 | lg.setLineWidth(1) 366 | lg.setColor(unpack(nc)) 367 | lg.ellipse('fill',x+w/2,y+h/2,w/2-2,h/2-2) 368 | if text then drawRadioButtonText(text,x+w+10,y,h) end 369 | if isChecked then 370 | lg.setColor(nc[1]-.25,nc[2]-.25,nc[3]-.25) 371 | lg.ellipse('fill',x+w/2,y+h/2,w/2-math.floor(1+w/5),h/2-math.floor(1+h/5),40) 372 | end 373 | end 374 | 375 | function theme.drawRadioButtonHover(isChecked,text,x,y,w,h) 376 | lg.setColor(hc[1]-.15,hc[2]-.15,hc[3]-.15) 377 | lg.setLineWidth(3) 378 | lg.ellipse('line',x+w/2,y+h/2,w/2,h/2,40) 379 | lg.setLineWidth(1) 380 | lg.setColor(unpack(hc)) 381 | lg.ellipse('fill',x+w/2,y+h/2,w/2-2,h/2-2) 382 | if text then drawRadioButtonText(text,x+w+10,y,h) end 383 | if isChecked then 384 | lg.setColor(hc[1]-.25,hc[2]-.25,hc[3]-.25) 385 | lg.ellipse('fill',x+w/2,y+h/2,w/2-math.floor(1+w/5),h/2-math.floor(1+h/5),40) 386 | end 387 | end 388 | 389 | function theme.drawRadioButtonPressed(isChecked,text,x,y,w,h) 390 | y=y+1 391 | lg.setColor(ac[1]-.2,ac[2]-.2,ac[3]-.2) 392 | lg.setLineWidth(3) 393 | lg.ellipse('line',x+w/2,y+h/2,w/2,h/2,40) 394 | lg.setLineWidth(1) 395 | lg.setColor(ac[1]-.05,ac[2]-.05,ac[3]-.05) 396 | lg.ellipse('fill',x+w/2,y+h/2,w/2-2,h/2-2) 397 | if text then drawRadioButtonText(text,x+w+10,y-1,h) end 398 | if isChecked then 399 | lg.setColor(ac[1]-.15,ac[2]-.15,ac[3]-.15) 400 | lg.ellipse('fill',x+w/2,y+h/2,w/2-math.floor(1+w/5),h/2-math.floor(1+h/5),40) 401 | end 402 | end 403 | 404 | -------------------CHECKBOX WIDGET---------------------- 405 | 406 | function theme.drawCheckBoxNormal(isChecked,text,x,y,w,h) 407 | lg.setColor(nc[1]-.05,nc[2]-.05,nc[3]-.05) 408 | lg.rectangle('fill',x,y,w,h+math.floor(1+h/16),2,2) 409 | lg.setColor(unpack(nc)) 410 | lg.rectangle('fill',x,y,w,h,2,2) 411 | if text then drawCheckBoxText(text,x+w+10,y,h) end 412 | if isChecked then 413 | lg.setColor(.3,.3,.3) 414 | drawTick(x,y,w,h) 415 | end 416 | end 417 | 418 | function theme.drawCheckBoxHover(isChecked,text,x,y,w,h) 419 | lg.setColor(hc[1]-.05,hc[2]-.05,hc[3]-.05) 420 | lg.rectangle('fill',x,y,w,h+math.floor(1+h/16),2,2) 421 | lg.setColor(unpack(hc)) 422 | lg.rectangle('fill',x,y,w,h,2,2) 423 | if text then drawCheckBoxText(text,x+w+10,y,h) end 424 | if isChecked then 425 | lg.setColor(.3,.3,.3) 426 | drawTick(x,y,w,h) 427 | end 428 | end 429 | 430 | function theme.drawCheckBoxPressed(isChecked,text,x,y,w,h) 431 | lg.setColor(ac[1]-.15,ac[2]-.15,ac[3]-.15) 432 | lg.rectangle('fill',x,y+2,w,h-2+math.floor(1+h/16),2,2) 433 | lg.setColor(unpack(ac)) 434 | lg.rectangle('fill',x,y+2,w,h-2,2,2) 435 | if text then drawCheckBoxText(text,x+w+10,y,h) end 436 | if isChecked then 437 | lg.setColor(.3,.3,.3) 438 | drawTick(x,y+2,w,h) 439 | end 440 | end 441 | 442 | -------------------BUTTON WIDGET---------------------- 443 | 444 | function theme.drawButtonNormal(text,x,y,w,h) 445 | lg.setColor(nc[1]-.05,nc[2]-.05,nc[3]-.05) 446 | lg.rectangle('fill',x,y,w,h+6,5,5) 447 | lg.setColor(unpack(nc)) 448 | lg.rectangle('fill',x,y,w,h,5,5) 449 | drawButtonText(text,x,y,w,h) 450 | end 451 | 452 | function theme.drawButtonHover(text,x,y,w,h) 453 | lg.setColor(hc[1]-.05,hc[2]-.05,hc[3]-.05) 454 | lg.rectangle('fill',x,y,w,h+6,5,5) 455 | lg.setColor(unpack(hc)) 456 | lg.rectangle('fill',x,y,w,h,5,5) 457 | drawButtonText(text,x,y,w,h) 458 | end 459 | 460 | function theme.drawButtonPressed(text,x,y,w,h) 461 | lg.setColor(ac[1]-.1,ac[2]-.1,ac[3]-.1) 462 | lg.rectangle('fill',x,y+3,w,h+3,5,5) 463 | lg.setColor(unpack(ac)) 464 | lg.rectangle('fill',x,y+3,w,h-3,5,5) 465 | drawButtonText(text,x,y+2,w,h) 466 | end 467 | 468 | return theme -------------------------------------------------------------------------------- /src/Widgets/Button.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws the button at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local uiState=require(LIB_PATH..'.Core.UIState') 10 | local util=require(LIB_PATH..'.Core.util') 11 | local theme=require(LIB_PATH..'.Core.theme') 12 | local core=require(LIB_PATH..'.Core') 13 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 14 | 15 | local function Button(text,x,y,w,h) 16 | if core.idle then return end 17 | local id=core.genID() 18 | text,x,y,w,h=util.getButtonParams(text,x,y,w,h) 19 | core.updateWidget(id,util.mouseOver(x,y,w,h)) 20 | if uiState.hotItem==id then 21 | if uiState.activeItem==id then 22 | DrawCommands.registerCommand(function() 23 | theme.drawButtonPressed(text,x,y,w,h) 24 | end) 25 | else 26 | DrawCommands.registerCommand(function() 27 | theme.drawButtonHover(text,x,y,w,h) 28 | end) 29 | end 30 | else 31 | DrawCommands.registerCommand(function() 32 | theme.drawButtonNormal(text,x,y,w,h) 33 | end) 34 | end 35 | 36 | return (uiState.hotItem==id and uiState.activeItem==id) or ( 37 | uiState.lastActiveItem==id and uiState.mouseUp 38 | ) 39 | end 40 | 41 | return Button -------------------------------------------------------------------------------- /src/Widgets/CheckBox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws a checkbox (with/without label) at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local uiState=require(LIB_PATH..'.Core.UIState') 10 | local util=require(LIB_PATH..'.Core.util') 11 | local theme=require(LIB_PATH..'.Core.theme') 12 | local core=require(LIB_PATH..'.Core') 13 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 14 | 15 | local function CheckBox(isChecked,text,x,y,w,h) 16 | if core.idle then return end 17 | local id=core.genID() 18 | isChecked,text,x,y,w,h=util.getCheckBoxParams(isChecked,text,x,y,w,h) 19 | --For the check-box and the label!! 20 | core.updateWidget( 21 | id, 22 | util.mouseOver(x,y,w,h) or 23 | (text and 24 | util.mouseOver( 25 | x+w+15,y,theme.getFontSize('checkbox',text),h 26 | ) 27 | ) 28 | ) 29 | if uiState.hotItem==id then 30 | if uiState.activeItem==id then 31 | DrawCommands.registerCommand(function() 32 | theme.drawCheckBoxPressed(isChecked[1],text,x,y,w,h) 33 | end) 34 | else 35 | DrawCommands.registerCommand(function() 36 | theme.drawCheckBoxHover(isChecked[1],text,x,y,w,h) 37 | end) 38 | end 39 | else 40 | DrawCommands.registerCommand(function() 41 | theme.drawCheckBoxNormal(isChecked[1],text,x,y,w,h) 42 | end) 43 | end 44 | if uiState.lastActiveItem==id then 45 | --If the last thing that was clicked was this check-box 46 | isChecked[1]=not isChecked[1] 47 | uiState.lastActiveItem=nil 48 | return true 49 | end 50 | end 51 | 52 | return CheckBox -------------------------------------------------------------------------------- /src/Widgets/Image.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws the Image at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local util=require(LIB_PATH..'.Core.util') 10 | local core=require(LIB_PATH..'.Core') 11 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 12 | 13 | local function Image(img,x,y,w,h) 14 | if core.idle then return end 15 | local imgColor,r 16 | img,imgColor,r,x,y,w,h=util.getImageParams(img,x,y,w,h) 17 | 18 | local imgW,imgH=img:getDimensions() 19 | DrawCommands.registerCommand(function() 20 | love.graphics.setColor(1,1,1) 21 | if imgColor then love.graphics.setColor(unpack(imgColor)) end 22 | love.graphics.draw(img,x,y,r,w/imgW,h/imgH,imgW/2,imgH/2) 23 | -- love.graphics.draw(img,x,y,0,1,1,w/2,h/2) 24 | end) 25 | end 26 | 27 | return Image -------------------------------------------------------------------------------- /src/Widgets/ImageButton.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws the Image at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local uiState=require(LIB_PATH..'.Core.UIState') 10 | local util=require(LIB_PATH..'.Core.util') 11 | local core=require(LIB_PATH..'.Core') 12 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 13 | 14 | local function ImageButton(img,x,y,w,h) 15 | if core.idle then return end 16 | 17 | local id=core.genID() 18 | x,y,w,h=util.getImageButtonParams(img,x,y,w,h) 19 | 20 | local imgW,imgH=img.image:getDimensions() 21 | local cond=util.mouseOver(x-w/2,y-h/2,w,h) 22 | if cond and img.precise then 23 | --If mouse is in the bounding-box and precise-mask is on then 24 | local imageData=img.data 25 | end 26 | core.updateWidget(id,cond) 27 | 28 | if uiState.hotItem==id then 29 | if uiState.activeItem==id then 30 | DrawCommands.registerCommand(function() 31 | love.graphics.setColor(1,1,1) 32 | if img.active then love.graphics.setColor(unpack(img.active)) end 33 | love.graphics.draw(img.image,x,y,img.rotation,w/imgW,h/imgH,imgW/2,imgH/2) 34 | end) 35 | else 36 | DrawCommands.registerCommand(function() 37 | love.graphics.setColor(1,1,1) 38 | if img.hover then love.graphics.setColor(unpack(img.hover)) end 39 | love.graphics.draw(img.image,x,y,img.rotation,w/imgW,h/imgH,imgW/2,imgH/2) 40 | end) 41 | end 42 | else 43 | DrawCommands.registerCommand(function() 44 | love.graphics.setColor(1,1,1) 45 | if img.default then love.graphics.setColor(unpack(img.default)) end 46 | love.graphics.draw(img.image,x,y,img.rotation,w/imgW,h/imgH,imgW/2,imgH/2) 47 | end) 48 | end 49 | 50 | -- love.graphics.rectangle('line',x-w/2,y-h/2,w,h) 51 | 52 | 53 | DrawCommands.registerCommand(function() 54 | love.graphics.setColor(1,1,1) 55 | 56 | 57 | end) 58 | 59 | return (uiState.hotItem==id and uiState.activeItem==id) or ( 60 | uiState.lastActiveItem==id and uiState.mouseUp 61 | ) 62 | end 63 | 64 | return ImageButton -------------------------------------------------------------------------------- /src/Widgets/Label.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws the label at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local theme=require(LIB_PATH..'.Core.theme') 10 | local util=require(LIB_PATH..'.Core.util') 11 | local core=require(LIB_PATH..'.Core') 12 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 13 | 14 | local function Label(text,x,y) 15 | if core.idle then return end 16 | x,y=util.getLabelParams(text,x,y) 17 | DrawCommands.registerCommand(function() 18 | if type(text)=='string' then 19 | theme.drawLabel(text,x,y) 20 | else 21 | love.graphics.setColor(1,1,1) 22 | if text.color then love.graphics.setColor(text.color) end 23 | if text.font then love.graphics.setFont(text.font) end 24 | love.graphics.print(text.text,x,y) 25 | end 26 | end) 27 | end 28 | 29 | return Label -------------------------------------------------------------------------------- /src/Widgets/Layout.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Layout is not exactly a "widget"! You can't "draw a layout"! 3 | What Layout does is - it helps in setting the position and dimension of a widget 4 | relatively! Layout are "row-based" where each row is divided into columns. 5 | By default there's only on column but you can change that! There's also 6 | something such as the position of the layout and the padding of the layout! 7 | ]] 8 | 9 | local WIDGETS_PATH = (...):match("^(.+)%.[^%.]+") 10 | 11 | local Window=require(WIDGETS_PATH..'.Window') 12 | 13 | local Layout={ 14 | flex, --if layout is flexible 15 | x=0,y=0; --position of the layout 16 | cx=0,cy=0; --position of the cursor 17 | rh=0; --max. height of the current row 18 | px=4,py=9; --layout padding! (has no effect on the first widget) 19 | cols=1; --widgets are first placed column-wise then layout moves to the next-row! 20 | col=1; --the current column! Layout will move to the next row when col>cols 21 | } 22 | 23 | --[[ 24 | NOTE 1: An important thing about layouts is that they affect only future widget calls!! 25 | NOTE 2: There are no "rows"! Number of rows can be infinite (theoretically)! 26 | ]] 27 | 28 | function Layout.setPadding(px,py) 29 | Layout.px,Layout.py=px,py 30 | end 31 | 32 | function Layout.setFlex(val) Layout.flex=val end 33 | 34 | --Modified Copy from util.lua 35 | 36 | local function getXFromAlign(align) 37 | if type(align)=='string' then 38 | if align=='center' then return Window.getCenterX() 39 | elseif align=='left' then return Window.getLeft() 40 | elseif align=='right' then return Window.getRight() 41 | else 42 | return tonumber(align)*Window.getDimensions() 43 | end 44 | else return align end 45 | end 46 | 47 | local function getYFromAlign(align) 48 | if type(align)=='string' then 49 | if align=='center' then return Window.getCenterY() 50 | elseif align=='top' then return Window.getTop() 51 | elseif align=='bottom' then return Window.getBottom() 52 | else 53 | local winW,winH=Window.getDimensions() 54 | return tonumber(align)*winH 55 | end 56 | else return align end 57 | end 58 | 59 | 60 | --Affects only the current row 61 | function Layout.setPosition(x,y,relative) 62 | x=getXFromAlign(x) or (relative and 0 or Layout.x) 63 | y=getYFromAlign(y) or (relative and 0 or Layout.y) 64 | Layout.x,Layout.y=x+(relative and Layout.x or 0),y+(relative and Layout.y or 0) 65 | end 66 | 67 | --Affects all future rows 68 | function Layout.setCursor(x,y,relative) 69 | x=getXFromAlign(x) or (relative and 0 or Layout.cx) 70 | y=getYFromAlign(y) or (relative and 0 or Layout.cy) 71 | Layout.cx,Layout.cy=x+(relative and Layout.cx or 0),y+(relative and Layout.cy or 0) 72 | Layout.setPosition(x,y,relative) 73 | end 74 | 75 | function Layout.setColumns(cols) 76 | Layout.cols=cols 77 | end 78 | 79 | function Layout.getPosition(w,h) 80 | local x,y=Layout.x,Layout.y 81 | if w then 82 | Layout.x=Layout.x+Layout.px+w 83 | Layout.col=Layout.col+1 84 | Layout.rh=math.max(Layout.rh,h) 85 | if Layout.col>Layout.cols or (Layout.flex and Layout.x+w+Layout.px>Window.getDimensions()) then 86 | Layout.moveToNextRow(h) 87 | end 88 | end 89 | return x,y 90 | end 91 | 92 | function Layout.reset(x,y) 93 | Layout.setPadding(4,9) 94 | Layout.setCursor(x or 0, y or 0) 95 | Layout.cols,Layout.col=1,1 96 | end 97 | 98 | --Normally an internal function but can be used externally without any problem! (hopefully) 99 | function Layout.moveToNextRow() 100 | Layout.x,Layout.col=Layout.cx,1 101 | Layout.y=Layout.y+Layout.py+Layout.rh 102 | Layout.rh=0 103 | end 104 | 105 | return Layout 106 | -------------------------------------------------------------------------------- /src/Widgets/Menu.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Menus generally don't need a menubar for them to rendered 3 | making them context-menus 4 | ]] 5 | 6 | local LIB_PATH = (...): 7 | match("^(.+)%.[^%.]+"): 8 | match("^(.+)%.[^%.]+") 9 | 10 | local uiState=require(LIB_PATH..'.Core.UIState') 11 | local util=require(LIB_PATH..'.Core.util') 12 | local theme=require(LIB_PATH..'.Core.theme') 13 | local core=require(LIB_PATH..'.Core') 14 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 15 | local Window=require(LIB_PATH..'.Widgets.Window') 16 | 17 | --Text of the menu and width of all the previous menus 18 | local width --why waste memory in stacks when a single variable can do the job! 19 | local menuText --same case here 20 | 21 | local function BeginMenu(text) 22 | if core.idle then return end 23 | menuText=text 24 | width=Window.getMenuWidth() 25 | local w,h=theme.getFontSize('menu',text..'a')--trick for padding! 26 | Window.addMenu(w) 27 | local x,y=Window.getTopLeft() x=x+width 28 | if util.mouseOver(x,y,w,h) then 29 | DrawCommands.registerCommand(-1,function() 30 | theme.drawMenuHover(text,x,y,w,h) 31 | end) 32 | else 33 | DrawCommands.registerCommand(-1,function() 34 | theme.drawMenuNormal(text,x,y) 35 | end) 36 | end 37 | return true 38 | end 39 | 40 | local function EndMenu() 41 | local text=menuText --IMPORTANT!!! This is how Lua works dude! 42 | 43 | end 44 | 45 | return {BeginMenu,EndMenu} 46 | 47 | --[[ 48 | WORK IN PROGRESS: Wanna help me out :> 49 | ]] -------------------------------------------------------------------------------- /src/Widgets/MenuBar.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MenuBar is a container for Menus! 3 | Menus generally don't need a menubar for them to rendered 4 | making them context-menus 5 | ]] 6 | 7 | local LIB_PATH = (...): 8 | match("^(.+)%.[^%.]+"): 9 | match("^(.+)%.[^%.]+") 10 | 11 | local uiState=require(LIB_PATH..'.Core.UIState') 12 | local util=require(LIB_PATH..'.Core.util') 13 | local theme=require(LIB_PATH..'.Core.theme') 14 | local core=require(LIB_PATH..'.Core') 15 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 16 | local Window=require(LIB_PATH..'.Widgets.Window') 17 | 18 | local function BeginMenuBar() 19 | Window.addMenuBar() 20 | return true 21 | end 22 | 23 | local function EndMenuBar() 24 | local x,y=Window.getTopLeft() 25 | local w,h=theme.getFontSize('menu','') 26 | w=Window.getDimensions() 27 | DrawCommands.registerCommand(function() 28 | theme.drawMenuBar(x,y,w,h) 29 | end) 30 | end 31 | 32 | return {BeginMenuBar,EndMenuBar} 33 | 34 | --[[ 35 | WORK IN PROGRESS: Wanna help me out :> 36 | ]] -------------------------------------------------------------------------------- /src/Widgets/MenuItem.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MenuItem is really just anything that goes into a menu! 3 | There's checked menu-item, radio-menu item and normal 4 | label-menu-item! 5 | ]] 6 | 7 | local LIB_PATH = (...): 8 | match("^(.+)%.[^%.]+"): 9 | match("^(.+)%.[^%.]+") 10 | 11 | local uiState=require(LIB_PATH..'.Core.UIState') 12 | local util=require(LIB_PATH..'.Core.util') 13 | local theme=require(LIB_PATH..'.Core.theme') 14 | local core=require(LIB_PATH..'.Core') 15 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 16 | local Window=require(LIB_PATH..'.Widgets.Window') 17 | 18 | local function BeginMenuBar() 19 | Window.addMenuBar() 20 | return true 21 | end 22 | 23 | local function EndMenuBar() 24 | local x,y=Window.getTopLeft() 25 | local w,h=theme.getFontSize('menu','') 26 | w=Window.getDimensions() 27 | DrawCommands.registerCommand(function() 28 | theme.drawMenuBar(x,y,w,h) 29 | end) 30 | end 31 | 32 | return {BeginMenuBar,EndMenuBar} 33 | 34 | --[[ 35 | WORK IN PROGRESS: Wanna help me out :> 36 | ]] -------------------------------------------------------------------------------- /src/Widgets/ProgressBar.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Progress Bar are very essential for loading screens and stuff like that! 3 | Because of the way Lovely-imGUI has been designed you can have variety 4 | of progress-bars with themes! If you want different progress bars 5 | without messing with themes then you should probably edit this file! 6 | Orientation of progress bars is again handled by themes which can make use 7 | of the style parameter that's passed in the last argument! 8 | ]] 9 | 10 | local LIB_PATH = (...): 11 | match("^(.+)%.[^%.]+"): 12 | match("^(.+)%.[^%.]+") 13 | 14 | local uiState=require(LIB_PATH..'.Core.UIState') 15 | local theme=require(LIB_PATH..'.Core.theme') 16 | local util=require(LIB_PATH..'.Core.util') 17 | local core=require(LIB_PATH..'.Core') 18 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 19 | 20 | local function ProgressBar(progressBar,x,y,w,h) 21 | if core.idle then return end 22 | x,y,w,h=util.getProgressBarParams(progressBar,x,y,w,h) 23 | progressBar._timer=progressBar._timer or 0 24 | progressBar._timer=progressBar._timer+uiState.dt 25 | --By default the progress is linear and lasts for 5 seconds! 26 | progressBar.update=progressBar.update or function(dt) return dt/5 end 27 | progressBar.value=progressBar.value or 0 28 | if progressBar.value<1 then 29 | local updateValue=progressBar.update(uiState.dt,progressBar._timer) 30 | progressBar.value=math.min(1,progressBar.value+updateValue) 31 | else 32 | progressBar._timer=0 33 | end 34 | DrawCommands.registerCommand(function() 35 | theme.drawProgressBar(progressBar.text,progressBar.value,x,y,w,h,progressBar.style) 36 | end) 37 | end 38 | 39 | return ProgressBar -------------------------------------------------------------------------------- /src/Widgets/RadioButton.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws a radio-button (with/without label) at a relative/absolute position 3 | ]] 4 | 5 | local LIB_PATH = (...): 6 | match("^(.+)%.[^%.]+"): 7 | match("^(.+)%.[^%.]+") 8 | 9 | local uiState=require(LIB_PATH..'.Core.UIState') 10 | local util=require(LIB_PATH..'.Core.util') 11 | local theme=require(LIB_PATH..'.Core.theme') 12 | local core=require(LIB_PATH..'.Core') 13 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 14 | 15 | local function RadioButton(rbtn,text,x,y,w,h) 16 | if core.idle then return end 17 | local id=core.genID() 18 | --TODO: Remove rbtn from getRadioParams for possible performance optimization! 19 | rbtn,text,x,y,w,h=util.getRadioButtonParams(rbtn,text,x,y,w,h) 20 | --For the check-box and the label!! 21 | core.updateWidget( 22 | id, 23 | util.mouseOver(x,y,w,h) or 24 | (text and 25 | util.mouseOver( 26 | x+w+15,y,theme.getFontSize('radiobutton',text),h 27 | ) 28 | ) 29 | ) 30 | if uiState.hotItem==id then 31 | if uiState.activeItem==id then 32 | DrawCommands.registerCommand(function() 33 | theme.drawRadioButtonPressed(rbtn.active,text,x,y,w,h) 34 | end) 35 | else 36 | DrawCommands.registerCommand(function() 37 | theme.drawRadioButtonHover(rbtn.active,text,x,y,w,h) 38 | end) 39 | end 40 | else 41 | DrawCommands.registerCommand(function() 42 | theme.drawRadioButtonNormal(rbtn.active,text,x,y,w,h) 43 | end) 44 | end 45 | --TODO: Make other radio buttons return 0 and this return 1 46 | if uiState.lastActiveItem==id then 47 | --If the last thing that was clicked was this radio-button 48 | rbtn.active=true 49 | if rbtn.group then 50 | for i=1,#rbtn.group do 51 | if rbtn.group[i]~=rbtn then 52 | rbtn.group[i].active=nil 53 | end 54 | end 55 | end 56 | uiState.lastActiveItem=nil 57 | return true 58 | end 59 | end 60 | 61 | return RadioButton -------------------------------------------------------------------------------- /src/Widgets/Scaler.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This draws the Scaler at a relative/absolute position! 3 | A Scaler is different than a slider in that it doesn't have 4 | a "thumb"! It also doesn't have to worry about slide-increments 5 | and stuff like that! 6 | Also a scaler have *orientation* instead of *direction*!! 7 | ]] 8 | 9 | local LIB_PATH = (...): 10 | match("^(.+)%.[^%.]+"): 11 | match("^(.+)%.[^%.]+") 12 | 13 | local uiState=require(LIB_PATH..'.Core.UIState') 14 | local util=require(LIB_PATH..'.Core.util') 15 | local theme=require(LIB_PATH..'.Core.theme') 16 | local core=require(LIB_PATH..'.Core') 17 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 18 | 19 | local function Scaler(scaler,x,y,w,h) 20 | if core.idle then return end 21 | local id=core.genID() 22 | 23 | x,y,w,h=util.getScalerParams(scaler,x,y,w,h) 24 | 25 | --Normalize the value of the scaler! 26 | local fraction = (scaler.value - scaler.min) / (scaler.max - scaler.min) 27 | core.pianoMode=not core.pianoMode 28 | core.updateWidget(id,util.mouseOver(x,y,w,h)) 29 | core.pianoMode=not core.pianoMode 30 | 31 | if uiState.activeItem==id then 32 | if scaler.orientation=='t-b' then 33 | fraction = math.min(1, math.max(0, (uiState.mouseY - y) / h)) 34 | elseif scaler.orientation=='b-t' then 35 | fraction = math.min(1, math.max(0, (y+h-uiState.mouseY) / h)) 36 | elseif scaler.orientation=='l-r' then 37 | fraction = math.min(1, math.max(0, (x+w-uiState.mouseX) / w)) 38 | else 39 | fraction = math.min(1, math.max(0, (uiState.mouseX - x) / w)) 40 | end 41 | DrawCommands.registerCommand(function() 42 | theme.drawScalerPressed(fraction,scaler.orientation,x,y,w,h,scaler.style) 43 | end) 44 | local v = fraction * (scaler.max - scaler.min) + scaler.min 45 | if v ~= scaler.value then 46 | scaler.value = v 47 | return true 48 | end 49 | elseif uiState.hotItem==id then 50 | DrawCommands.registerCommand(function() 51 | theme.drawScalerHover(fraction,scaler.orientation,x,y,w,h,scaler.style) 52 | end) 53 | else 54 | DrawCommands.registerCommand(function() 55 | theme.drawScalerNormal(fraction,scaler.orientation,x,y,w,h,scaler.style) 56 | end) 57 | end 58 | end 59 | 60 | return Scaler -------------------------------------------------------------------------------- /src/Widgets/Slider.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A Slider is slightly more complicated variant of slider! 3 | It has increments and a "thumb" and by default it doesn't have 4 | a progress bar but themes can go nasty and implement it! 5 | Unlike Scalers, Sliders do not have to worry about orientation! 6 | ]] 7 | 8 | local LIB_PATH = (...): 9 | match("^(.+)%.[^%.]+"): 10 | match("^(.+)%.[^%.]+") 11 | 12 | local uiState=require(LIB_PATH..'.Core.UIState') 13 | local util=require(LIB_PATH..'.Core.util') 14 | local theme=require(LIB_PATH..'.Core.theme') 15 | local core=require(LIB_PATH..'.Core') 16 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 17 | 18 | local function Slider(slider,x,y,w,h) 19 | if core.idle then return end 20 | local fraction,sx,sy,sw,sh --thumb dimenions and position 21 | local id=core.genID() 22 | fraction,x,y,w,h,sx,sy,sw,sh=util.getSliderParams(slider,x,y,w,h) 23 | 24 | core.pianoMode=not core.pianoMode 25 | core.updateWidget(id,util.mouseOver(sx,sy,sw,sh)) --remove 's' if you want to disable "thumb only"! 26 | core.pianoMode=not core.pianoMode 27 | 28 | if uiState.activeItem==id then 29 | if slider.vertical then 30 | fraction = math.min(1, math.max(0, (uiState.mouseY - y) / h)) 31 | else 32 | fraction = math.min(1, math.max(0, (uiState.mouseX - x) / w)) 33 | end 34 | DrawCommands.registerCommand(function() 35 | theme.drawSliderPressed(fraction,slider.vertical,x,y,w,h,slider.style) 36 | end) 37 | local v = fraction * (slider.max - slider.min) + slider.min 38 | if v ~= slider.value then 39 | slider.value = v 40 | return true 41 | end 42 | elseif uiState.hotItem==id then 43 | DrawCommands.registerCommand(function() 44 | theme.drawSliderHover(fraction,slider.vertical,x,y,w,h,slider.style) 45 | end) 46 | else 47 | DrawCommands.registerCommand(function() 48 | theme.drawSliderNormal(fraction,slider.vertical,x,y,w,h,slider.style) 49 | end) 50 | end 51 | end 52 | 53 | return Slider -------------------------------------------------------------------------------- /src/Widgets/Spinner.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Text-Entry + Two up-and-down buttons 3 | I would appreciate your help in this!! 4 | ]] -------------------------------------------------------------------------------- /src/Widgets/Stepper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Stepper is basically a label+two buttons that may-be top-and-down 3 | or left-and-right depending on direction! 4 | ]] 5 | 6 | 7 | --[[ 8 | WORK IN PROGRESS: Wanna help me out :> 9 | ]] 10 | 11 | 12 | local LIB_PATH = (...): 13 | match("^(.+)%.[^%.]+"): 14 | match("^(.+)%.[^%.]+") 15 | 16 | local uiState=require(LIB_PATH..'.Core.UIState') 17 | local util=require(LIB_PATH..'.Core.util') 18 | local param=require(LIB_PATH..'.Core.param') 19 | local theme=require(LIB_PATH..'.Core.theme') 20 | local core=require(LIB_PATH..'.Core') 21 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 22 | 23 | local function Stepper(stepper,x,y,w,h) 24 | if core.idle then return end 25 | local id=core.genID() 26 | x,y,w,h=util.getStepperParams(stepper,x,y,w,h) 27 | local lx,ly,lw,lh=param.getStepperLeftButton(stepper,x,y,w,h) 28 | local rx,ry,rw,rh=param.getStepperRightButton(stepper,x,y,w,h) 29 | core.updateWidget( 30 | id, 31 | util.mouseOver(lx,ly,lw,lh) or util.mouseOver(rx,ry,rw,rh) 32 | ) 33 | local updated 34 | if core.isMouseReleased() then 35 | if util.mouseOver(lx,ly,lw,lh) then 36 | stepper.active=util.warp(stepper.active-1,1,#stepper.list) 37 | updated=true 38 | elseif util.mouseOver(rx,ry,rw,rh) then 39 | stepper.active=util.warp(stepper.active+1,1,#stepper.list) 40 | updated=true 41 | end 42 | 43 | end 44 | if uiState.hotItem==id then 45 | if uiState.activeItem==id then 46 | DrawCommands.registerCommand(function() 47 | theme.drawStepperPressed(stepper,util.mouseOver(lx,ly,lw,lh),x,y,w,h) 48 | end) 49 | else 50 | DrawCommands.registerCommand(function() 51 | theme.drawStepperHover(stepper,util.mouseOver(lx,ly,lw,lh),x,y,w,h) 52 | end) 53 | end 54 | else 55 | DrawCommands.registerCommand(function() 56 | theme.drawStepperNormal(stepper,x,y,w,h) 57 | end) 58 | end 59 | 60 | return updated 61 | end 62 | 63 | return Stepper -------------------------------------------------------------------------------- /src/Widgets/TextEntry.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A TextEntry is similar to Java Swing TextField - it's single-line 3 | and when you hit enter it's done! 4 | ]] 5 | 6 | 7 | --[[ 8 | WORK IN PROGRESS: Wanna help me out :> 9 | TODO: Auto-scrolling for text (_offset here means just that!) 10 | TODO: Add selection support! 11 | ]] 12 | 13 | 14 | local LIB_PATH = (...): 15 | match("^(.+)%.[^%.]+"): 16 | match("^(.+)%.[^%.]+") 17 | 18 | local uiState=require(LIB_PATH..'.Core.UIState') 19 | local util=require(LIB_PATH..'.Core.util') 20 | local theme=require(LIB_PATH..'.Core.theme') 21 | local core=require(LIB_PATH..'.Core') 22 | local DrawCommands=require(LIB_PATH..'.Core.drawCommands') 23 | 24 | local function split(str, pos) return str:sub(1, pos), str:sub(1+pos) end 25 | 26 | local function TextEntry(textEntry,x,y,w,h) 27 | if core.idle then return end 28 | local id=core.genID() 29 | x,y,w,h=util.getTextEntryParams(textEntry,x,y,w,h) 30 | core.updateWidget(id,util.mouseOver(x,y,w,h)) 31 | if uiState.lastActiveItem==id then 32 | if uiState.keyChar then 33 | if not textEntry.limit or textEntry.text:len()