├── 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()