initial commit
3
example/conf.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
function love.conf (t)
|
||||||
|
t.window.resizable = true
|
||||||
|
end
|
||||||
12
example/font/liberation/AUTHORS
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
AUTHORS
|
||||||
|
|
||||||
|
Current Contributors (sorted alphabetically):
|
||||||
|
- Pravin Satpute <psatpute at redhat dot com>
|
||||||
|
Project Owner (Current)
|
||||||
|
Red Hat, Inc.
|
||||||
|
|
||||||
|
Previous Contributors
|
||||||
|
|
||||||
|
- Steve Matteson
|
||||||
|
Original Designer
|
||||||
|
Ascender, Inc.
|
||||||
14
example/font/liberation/ChangeLog
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
* Thu Oct 04 2012 Pravin Satpute <psatpute AT redhat DOT com>
|
||||||
|
- Resolved "Glyphs with multiple unicode encodings inhibit subsetting" #851790
|
||||||
|
- Resolved #851791, #854601 and #851825
|
||||||
|
- Following GASP table version as per Liberation old version. (Anti-aliasing disabled)
|
||||||
|
- Added support for Serbian glyphs for wikipedia #657849
|
||||||
|
- In Monospace fonts, isFixedPitch bit set via script for getting it recognized as Monospace in putty.exe
|
||||||
|
|
||||||
|
* Fri Jul 06 2012 Pravin Satpute <psatpute AT redhat DOT com>
|
||||||
|
- Initial version of Liberation fonts based on croscore fonts version 1.21.0
|
||||||
|
- Converted TTF files into SFD files to be open source.
|
||||||
|
- Update Copyright and License file
|
||||||
|
- set fsType bit to 0, Installable Embedding is allowed.
|
||||||
|
- Absolute value in HHeadAscent/Descent values for maintaining Metric compatibility.
|
||||||
|
|
||||||
102
example/font/liberation/LICENSE
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
Digitized data copyright (c) 2010 Google Corporation
|
||||||
|
with Reserved Font Arimo, Tinos and Cousine.
|
||||||
|
Copyright (c) 2012 Red Hat, Inc.
|
||||||
|
with Reserved Font Name Liberation.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License,
|
||||||
|
Version 1.1.
|
||||||
|
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
|
||||||
|
PREAMBLE The goals of the Open Font License (OFL) are to stimulate
|
||||||
|
worldwide development of collaborative font projects, to support the font
|
||||||
|
creation efforts of academic and linguistic communities, and to provide
|
||||||
|
a free and open framework in which fonts may be shared and improved in
|
||||||
|
partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves.
|
||||||
|
The fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply to
|
||||||
|
any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such.
|
||||||
|
This may include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components
|
||||||
|
as distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting ? in part or in whole ?
|
||||||
|
any of the components of the Original Version, by changing formats or
|
||||||
|
by porting the Font Software to a new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical writer
|
||||||
|
or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,in
|
||||||
|
Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the
|
||||||
|
corresponding Copyright Holder. This restriction only applies to the
|
||||||
|
primary font name as presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole, must
|
||||||
|
be distributed entirely under this license, and must not be distributed
|
||||||
|
under any other license. The requirement for fonts to remain under
|
||||||
|
this license does not apply to any document created using the Font
|
||||||
|
Software.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are not met.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||||
|
DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
BIN
example/font/liberation/LiberationMono-Bold.ttf
Normal file
BIN
example/font/liberation/LiberationMono-BoldItalic.ttf
Normal file
BIN
example/font/liberation/LiberationMono-Italic.ttf
Normal file
BIN
example/font/liberation/LiberationMono-Regular.ttf
Normal file
BIN
example/font/liberation/LiberationSans-Bold.ttf
Normal file
BIN
example/font/liberation/LiberationSans-BoldItalic.ttf
Normal file
BIN
example/font/liberation/LiberationSans-Italic.ttf
Normal file
BIN
example/font/liberation/LiberationSans-Regular.ttf
Normal file
BIN
example/font/liberation/LiberationSerif-Bold.ttf
Normal file
BIN
example/font/liberation/LiberationSerif-BoldItalic.ttf
Normal file
BIN
example/font/liberation/LiberationSerif-Italic.ttf
Normal file
BIN
example/font/liberation/LiberationSerif-Regular.ttf
Normal file
80
example/font/liberation/README
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
1. What's this?
|
||||||
|
=================
|
||||||
|
|
||||||
|
The Liberation Fonts is font collection which aims to provide document
|
||||||
|
layout compatibility as usage of Times New Roman, Arial, Courier New.
|
||||||
|
|
||||||
|
|
||||||
|
2. Requirements
|
||||||
|
=================
|
||||||
|
|
||||||
|
* fontforge is installed.
|
||||||
|
(http://fontforge.sourceforge.net)
|
||||||
|
|
||||||
|
|
||||||
|
3. Install
|
||||||
|
============
|
||||||
|
|
||||||
|
3.1 Decompress tarball
|
||||||
|
|
||||||
|
You can extract the files by following command:
|
||||||
|
|
||||||
|
$ tar zxvf liberation-fonts-[VERSION].tar.gz
|
||||||
|
|
||||||
|
3.2 Build from the source
|
||||||
|
|
||||||
|
Change into directory liberation-fonts-[VERSION]/ and build from sources by
|
||||||
|
following commands:
|
||||||
|
|
||||||
|
$ cd liberation-fonts-[VERSION]
|
||||||
|
$ make
|
||||||
|
|
||||||
|
The built font files will be available in 'build' directory.
|
||||||
|
|
||||||
|
3.3 Install to system
|
||||||
|
|
||||||
|
For Fedora, you could manually install the fonts by copying the TTFs to
|
||||||
|
~/.fonts for user wide usage, or to /usr/share/fonts/truetype/liberation
|
||||||
|
for system-wide availability. Then, run "fc-cache" to let that cached.
|
||||||
|
|
||||||
|
For other distributions, please check out corresponding documentation.
|
||||||
|
|
||||||
|
|
||||||
|
4. Usage
|
||||||
|
==========
|
||||||
|
|
||||||
|
Simply select preferred liberation font in applications and start using.
|
||||||
|
|
||||||
|
|
||||||
|
5. License
|
||||||
|
============
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License,
|
||||||
|
Version 1.1.
|
||||||
|
|
||||||
|
Please read file "LICENSE" for details.
|
||||||
|
|
||||||
|
|
||||||
|
6. For Maintainers
|
||||||
|
====================
|
||||||
|
|
||||||
|
Before packaging a new release based on a new source tarball, you have to
|
||||||
|
update the version suffix in the Makefile:
|
||||||
|
|
||||||
|
VER = [VERSION]
|
||||||
|
|
||||||
|
Make sure that the defined version corresponds to the font software metadata
|
||||||
|
which you can check with ftinfo/otfinfo or fontforge itself. It is highly
|
||||||
|
recommended that file 'ChangeLog' is updated to reflect changes.
|
||||||
|
|
||||||
|
Create a tarball with the following command:
|
||||||
|
|
||||||
|
$ make dist
|
||||||
|
|
||||||
|
The new versioned tarball will be available in the dist/ folder as
|
||||||
|
'liberation-fonts-[NEW_VERSION].tar.gz'.
|
||||||
|
|
||||||
|
7. Credits
|
||||||
|
============
|
||||||
|
|
||||||
|
Please read file "AUTHORS" for list of contributors.
|
||||||
5
example/font/liberation/TODO
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Here are todo for next release
|
||||||
|
1) Serbian glyph for wikipedia https://bugzilla.redhat.com/show_bug.cgi?id=657849
|
||||||
|
- Improving shape of S_BE https://bugzilla.redhat.com/show_bug.cgi?id=657849#c96
|
||||||
|
2) Liberation Mono not recognizing as Mono in Windows application #861003
|
||||||
|
- presently it is patch, we have to update zero width characters to fixed width
|
||||||
BIN
example/icon/emblem-default.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
example/icon/emblem-documents.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
example/icon/emblem-downloads.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
example/icon/emblem-favorite.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
example/icon/emblem-generic.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
example/icon/emblem-important.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
example/icon/emblem-mail.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
example/icon/emblem-new.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
example/icon/emblem-package.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
example/icon/emblem-photos.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
example/icon/emblem-readonly.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
example/icon/emblem-shared.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
example/icon/emblem-symbolic-link.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
example/icon/emblem-synchronizing.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
example/icon/emblem-system.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
example/icon/emblem-unreadable.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
example/icon/emblem-urgent.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
example/icon/emblem-web.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
1
example/luigi
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../luigi/
|
||||||
124
example/main.lua
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
local Layout = require 'luigi.layout'
|
||||||
|
|
||||||
|
local style = {
|
||||||
|
mainWindow = {
|
||||||
|
width = 600,
|
||||||
|
height = 400,
|
||||||
|
},
|
||||||
|
short = {
|
||||||
|
height = 36,
|
||||||
|
},
|
||||||
|
toolbar = {
|
||||||
|
style = { 'short' },
|
||||||
|
},
|
||||||
|
toolButton = {
|
||||||
|
align = 'center middle',
|
||||||
|
width = 36,
|
||||||
|
margin = 4,
|
||||||
|
},
|
||||||
|
toolButton_not_hovered = {
|
||||||
|
outline = false,
|
||||||
|
bend = 0,
|
||||||
|
},
|
||||||
|
statusbar = {
|
||||||
|
style = 'panel',
|
||||||
|
align = 'left middle',
|
||||||
|
},
|
||||||
|
listThing = {
|
||||||
|
style = { 'short', 'panel' },
|
||||||
|
align = 'left middle',
|
||||||
|
outline = { 200, 200, 200 },
|
||||||
|
height = 80,
|
||||||
|
padding = 8,
|
||||||
|
background = { 255, 255, 255 },
|
||||||
|
bend = 0.2,
|
||||||
|
icon = 'icon/emblem-system.png',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local mainForm = { title = "Test window", id = 'mainWindow', type = 'panel',
|
||||||
|
{ type = 'panel', id = 'toolbar', flow = 'x',
|
||||||
|
{ type = 'button', id = 'newButton', style = 'toolButton',
|
||||||
|
icon = 'icon/emblem-default.png' },
|
||||||
|
{ type = 'button', id = 'loadButton', style = 'toolButton',
|
||||||
|
icon = 'icon/emblem-documents.png' },
|
||||||
|
{ type = 'button', id = 'saveButton', style = 'toolButton',
|
||||||
|
icon = 'icon/emblem-downloads.png' },
|
||||||
|
},
|
||||||
|
{ flow = 'x',
|
||||||
|
{ id = 'leftSideBox', width = 200,
|
||||||
|
{ text = 'Hi, I\'m some centered text. ', style = 'listThing',
|
||||||
|
align = 'middle center' },
|
||||||
|
{ text = 'A man, a plan, a canal: Panama!', style = 'listThing' },
|
||||||
|
},
|
||||||
|
{ type = 'sash', width = 4, },
|
||||||
|
{ id = 'mainCanvas' },
|
||||||
|
{ type = 'sash', width = 4, },
|
||||||
|
{ type = 'panel', id = 'rightSideBox', width = 200,
|
||||||
|
{ type = 'panel', text = 'A slider', align = 'bottom', height = 24 },
|
||||||
|
{ type = 'slider', height = 48, },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type = 'sash', height = 4, },
|
||||||
|
{ type = 'panel', flow = 'x', height = 48,
|
||||||
|
{ type = 'text', id = 'aTextField', text = 'a text field',
|
||||||
|
font = 'font/liberation/LiberationMono-Regular.ttf' },
|
||||||
|
{ type = 'button', width = 80, id = 'aButton', text = 'Styling!' },
|
||||||
|
},
|
||||||
|
{ type = 'panel', height = 24, id = 'statusbar', textColor = { 255, 0, 0 } },
|
||||||
|
}
|
||||||
|
|
||||||
|
local layout = Layout(mainForm)
|
||||||
|
|
||||||
|
layout:setStyle(style)
|
||||||
|
layout:setTheme(require 'luigi.theme.light')
|
||||||
|
|
||||||
|
layout.leftSideBox:addChild {
|
||||||
|
text = 'Alright man this is a great song\nwith a really long title...',
|
||||||
|
style = 'listThing',
|
||||||
|
align = 'middle right'
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
local KEY_ESCAPE = 27
|
||||||
|
|
||||||
|
layout:onKeyboard(function(event)
|
||||||
|
if event.key == KEY_ESCAPE then
|
||||||
|
layout.window:destroy()
|
||||||
|
os.exit(0)
|
||||||
|
end
|
||||||
|
if key == GLUT_KEY_F11 then
|
||||||
|
glutFullScreen()
|
||||||
|
end
|
||||||
|
if key == GLUT_KEY_F12 then
|
||||||
|
glutPositionWindow(-1, -1)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
]]
|
||||||
|
|
||||||
|
layout:onMotion(function(event)
|
||||||
|
local w = event.target
|
||||||
|
layout.statusbar.text = (w.id or '(unnamed)') .. ' ' ..
|
||||||
|
w:getX() .. ', ' .. w:getY() .. ' | ' ..
|
||||||
|
w:getWidth() .. 'x' .. w:getHeight()
|
||||||
|
layout.statusbar:update()
|
||||||
|
end)
|
||||||
|
|
||||||
|
layout.newButton:onMotion(function(event)
|
||||||
|
layout.statusbar.text = 'Create a new thing'
|
||||||
|
layout.statusbar:update()
|
||||||
|
return false
|
||||||
|
end)
|
||||||
|
|
||||||
|
layout.newButton:onPress(function(event)
|
||||||
|
print('creating a new thing!')
|
||||||
|
end)
|
||||||
|
|
||||||
|
layout.mainCanvas.text = [[Abedede sdfsdf asfdsdfdsfs sdfsdfsdf
|
||||||
|
sfsdfdfbv db er erg rth tryj ty j fgh dfgv
|
||||||
|
wefwef rgh erh rth e rgs dvg eh tyj rt h erg
|
||||||
|
erge rg eg erg er ergs erg er ge rh erh rth]]
|
||||||
|
|
||||||
|
layout.mainCanvas.align = 'top'
|
||||||
|
|
||||||
|
layout:show()
|
||||||
12
luigi/base.lua
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
return {
|
||||||
|
extend = function (self, subtype)
|
||||||
|
return setmetatable(subtype or {}, {
|
||||||
|
__index = self,
|
||||||
|
__call = function (self, ...)
|
||||||
|
local instance = setmetatable({}, { __index = self })
|
||||||
|
return instance, instance:constructor(...)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
constructor = function () end,
|
||||||
|
}
|
||||||
49
luigi/event.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
|
||||||
|
local Event = Base:extend({ name = 'Event' })
|
||||||
|
|
||||||
|
function Event:emit (observer, data, defaultAction)
|
||||||
|
local callbacks = self.registry[observer]
|
||||||
|
if not callbacks then
|
||||||
|
if defaultAction then defaultAction() end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i, callback in ipairs(callbacks) do
|
||||||
|
local result = callback(data or {})
|
||||||
|
if result ~= nil then return result end
|
||||||
|
end
|
||||||
|
if defaultAction then defaultAction() end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Event:bind (observer, callback)
|
||||||
|
local registry = self.registry
|
||||||
|
if not registry[observer] then
|
||||||
|
registry[observer] = {}
|
||||||
|
end
|
||||||
|
table.insert(registry[observer], callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
local eventNames = {
|
||||||
|
'Display', 'Keyboard', 'Motion', 'Mouse', 'Reshape', 'Enter', 'Leave',
|
||||||
|
'Press', 'PressStart', 'PressDrag', 'PressMove', 'PressLeave', 'PressEnter',
|
||||||
|
'PressEnd'
|
||||||
|
}
|
||||||
|
|
||||||
|
local weakKeyMeta = { __mode = 'k' }
|
||||||
|
|
||||||
|
for i, name in ipairs(eventNames) do
|
||||||
|
Event[name] = Event:extend({
|
||||||
|
name = name,
|
||||||
|
registry = setmetatable({}, weakKeyMeta),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Event.injectBinders (t)
|
||||||
|
for i, name in ipairs(eventNames) do
|
||||||
|
t['on' .. name] = function (...) return Event[name]:bind(...) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Event
|
||||||
60
luigi/font.lua
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
|
||||||
|
local Font = Base:extend()
|
||||||
|
|
||||||
|
local cache = {}
|
||||||
|
|
||||||
|
function Font:constructor (path, size, color)
|
||||||
|
if not size then
|
||||||
|
size = 12
|
||||||
|
end
|
||||||
|
if not color then
|
||||||
|
color = { 0, 0, 0 }
|
||||||
|
end
|
||||||
|
local key = (path or '') .. '_' .. size
|
||||||
|
|
||||||
|
if not cache[key] then
|
||||||
|
if path then
|
||||||
|
cache[key] = love.graphics.newFont(path, size)
|
||||||
|
else
|
||||||
|
cache[key] = love.graphics.newFont(size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.layout = {}
|
||||||
|
self.font = cache[key]
|
||||||
|
self.color = color
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:setAlignment (align)
|
||||||
|
self.layout.align = align
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:setWidth (width)
|
||||||
|
self.layout.width = width
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:getLineHeight ()
|
||||||
|
return self.font:getLineHeight()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:getAscender ()
|
||||||
|
return self.font:getAscent()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:getDescender ()
|
||||||
|
return self.font:getDescent()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:getAdvance (text)
|
||||||
|
return (self.font:getWidth(text))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Font:getWrappedHeight (text)
|
||||||
|
local _, lines = self.font:getWrap(text, self.layout.width)
|
||||||
|
return #lines * self.font:getHeight()
|
||||||
|
end
|
||||||
|
|
||||||
|
return Font
|
||||||
65
luigi/hooker.lua
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
local Hooker = {}
|
||||||
|
|
||||||
|
local wrapped = {}
|
||||||
|
|
||||||
|
local hooks = {}
|
||||||
|
|
||||||
|
local function hook (key, func)
|
||||||
|
if not func then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local next = hooks[key]
|
||||||
|
local item = { next = next, unhook = unhook, key = key, func = func }
|
||||||
|
|
||||||
|
if next then
|
||||||
|
next.prev = item
|
||||||
|
end
|
||||||
|
|
||||||
|
hooks[key] = item
|
||||||
|
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unhook (item)
|
||||||
|
if item.prev then
|
||||||
|
item.prev.next = item.next
|
||||||
|
end
|
||||||
|
|
||||||
|
if item.next then
|
||||||
|
item.next.prev = item.prev
|
||||||
|
end
|
||||||
|
|
||||||
|
if hooks[item.key] == item then
|
||||||
|
hooks[item.key] = item.next
|
||||||
|
end
|
||||||
|
|
||||||
|
item.prev = nil
|
||||||
|
item.next = nil
|
||||||
|
item.func = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hooker.hook (key, func)
|
||||||
|
if not wrapped[key] then
|
||||||
|
wrapped[key] = true
|
||||||
|
|
||||||
|
hook(key, love[key])
|
||||||
|
|
||||||
|
love[key] = function (...)
|
||||||
|
local item = hooks[key]
|
||||||
|
|
||||||
|
while item do
|
||||||
|
item.func(...)
|
||||||
|
item = item.next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return hook(key, func)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Hooker.unhook (item)
|
||||||
|
return unhook(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Hooker
|
||||||
167
luigi/input.lua
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
local Event = require(ROOT .. 'event')
|
||||||
|
local Renderer = require(ROOT .. 'renderer')
|
||||||
|
|
||||||
|
local Input = Base:extend()
|
||||||
|
|
||||||
|
local weakValueMeta = { __mode = 'v' }
|
||||||
|
|
||||||
|
function Input:constructor (layout)
|
||||||
|
self.layout = layout
|
||||||
|
self.pressedWidgets = setmetatable({}, weakValueMeta)
|
||||||
|
self.passedWidgets = setmetatable({}, weakValueMeta)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:bubbleEvent (eventName, widget, data)
|
||||||
|
local event = Event[eventName]
|
||||||
|
for ancestor in widget:getAncestors(true) do
|
||||||
|
local result = event:emit(ancestor, data)
|
||||||
|
if result ~= nil then return result end
|
||||||
|
end
|
||||||
|
return event:emit(self.layout, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handleDisplay ()
|
||||||
|
local root = self.layout.root
|
||||||
|
if root then Renderer:render(root) end
|
||||||
|
Event.Display:emit(self.layout)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handleKeyboard (key, x, y)
|
||||||
|
local widget = self.layout.focusedWidget or self.layout:getWidgetAt(x, y)
|
||||||
|
self:bubbleEvent('Keyboard', widget, {
|
||||||
|
target = widget,
|
||||||
|
key = key, x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handleMotion (x, y)
|
||||||
|
local widget = self.layout:getWidgetAt(x, y)
|
||||||
|
local previousWidget = self.previousMotionWidget
|
||||||
|
if not widget.hovered then
|
||||||
|
if previousWidget then
|
||||||
|
previousWidget.hovered = nil
|
||||||
|
end
|
||||||
|
widget.hovered = true
|
||||||
|
-- self.layout:update()
|
||||||
|
widget:update()
|
||||||
|
end
|
||||||
|
self:bubbleEvent('Motion', widget, {
|
||||||
|
target = widget,
|
||||||
|
oldTarget = previousWidget,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
if widget ~= previousWidget then
|
||||||
|
if previousWidget then
|
||||||
|
self:bubbleEvent('Leave', previousWidget, {
|
||||||
|
target = previousWidget,
|
||||||
|
newTarget = widget,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self:bubbleEvent('Enter', widget, {
|
||||||
|
target = widget,
|
||||||
|
oldTarget = previousWidget,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
self.previousMotionWidget = widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handlePressedMotion (x, y)
|
||||||
|
local widget = self.layout:getWidgetAt(x, y)
|
||||||
|
for button = 0, 2 do
|
||||||
|
local originWidget = self.pressedWidgets[button]
|
||||||
|
local passedWidget = self.passedWidgets[button]
|
||||||
|
if originWidget then
|
||||||
|
self:bubbleEvent('PressDrag', originWidget, {
|
||||||
|
target = originWidget,
|
||||||
|
newTarget = widget,
|
||||||
|
button = button,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
if (widget == passedWidget) then
|
||||||
|
self:bubbleEvent('PressMove', widget, {
|
||||||
|
target = widget,
|
||||||
|
origin = originWidget,
|
||||||
|
button = button,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
else
|
||||||
|
originWidget.pressed = (widget == originWidget) or nil
|
||||||
|
originWidget:update()
|
||||||
|
-- self.layout:update()
|
||||||
|
if passedWidget then
|
||||||
|
self:bubbleEvent('PressLeave', passedWidget, {
|
||||||
|
target = passedWidget,
|
||||||
|
newTarget = widget,
|
||||||
|
origin = originWidget,
|
||||||
|
button = button,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self:bubbleEvent('PressEnter', widget, {
|
||||||
|
target = widget,
|
||||||
|
oldTarget = passedWidget,
|
||||||
|
origin = originWidget,
|
||||||
|
button = button,
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
self.passedWidgets[button] = widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handlePressStart (button, x, y)
|
||||||
|
local widget = self.layout:getWidgetAt(x, y)
|
||||||
|
widget.pressed = true
|
||||||
|
-- self.layout:update()
|
||||||
|
widget:update()
|
||||||
|
self.pressedWidgets[button] = widget
|
||||||
|
self.passedWidgets[button] = widget
|
||||||
|
self:bubbleEvent('PressStart', widget, {
|
||||||
|
target = widget,
|
||||||
|
buton = button, x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handlePressEnd (button, x, y)
|
||||||
|
local widget = self.layout:getWidgetAt(x, y)
|
||||||
|
local originWidget = self.pressedWidgets[button]
|
||||||
|
originWidget.pressed = nil
|
||||||
|
-- self.layout:update()
|
||||||
|
originWidget:update()
|
||||||
|
self:bubbleEvent('PressEnd', widget, {
|
||||||
|
target = widget,
|
||||||
|
origin = originWidget,
|
||||||
|
buton = button, x = x, y = y
|
||||||
|
})
|
||||||
|
if (widget == originWidget) then
|
||||||
|
self:bubbleEvent('Press', widget, {
|
||||||
|
target = widget,
|
||||||
|
buton = button, x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.pressedWidgets[button] = nil
|
||||||
|
self.passedWidgets[button] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input:handleReshape (width, height)
|
||||||
|
local layout = self.layout
|
||||||
|
local root = layout.root
|
||||||
|
for i, widget in ipairs(layout.widgets) do
|
||||||
|
widget.position = {}
|
||||||
|
widget.dimensions = {}
|
||||||
|
end
|
||||||
|
root.width = width
|
||||||
|
root.height = height
|
||||||
|
Event.Reshape:emit(root, {
|
||||||
|
target = root,
|
||||||
|
width = width, height = height
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return Input
|
||||||
83
luigi/layout.lua
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
local Event = require(ROOT .. 'event')
|
||||||
|
local Window = require(ROOT .. 'window')
|
||||||
|
local Widget = require(ROOT .. 'widget')
|
||||||
|
local Input = require(ROOT .. 'input')
|
||||||
|
local Style = require(ROOT .. 'style')
|
||||||
|
|
||||||
|
local Layout = Base:extend()
|
||||||
|
|
||||||
|
local weakValueMeta = { __mode = 'v' }
|
||||||
|
|
||||||
|
function Layout:constructor (data)
|
||||||
|
self.widgets = setmetatable({}, weakValueMeta)
|
||||||
|
self.root = Widget.create(self, data or {})
|
||||||
|
self:setStyle()
|
||||||
|
self:setTheme()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:setStyle (rules)
|
||||||
|
self.style = Style(rules or {}, 'id', 'style')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:setTheme (rules)
|
||||||
|
self.theme = Style(rules or {}, 'type')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:show ()
|
||||||
|
local root = self.root
|
||||||
|
local width = root.width
|
||||||
|
local height = root.height
|
||||||
|
local title = root.title
|
||||||
|
if not self.input then
|
||||||
|
self.input = Input(self)
|
||||||
|
end
|
||||||
|
if not self.window then
|
||||||
|
self.window = Window(self.input)
|
||||||
|
end
|
||||||
|
self.window:show(width, height, title)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Layout:hide ()
|
||||||
|
self.window:hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the display. Call this after you change widget properties
|
||||||
|
-- that affect display.
|
||||||
|
function Layout:update (reshape)
|
||||||
|
self.window:update(reshape)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get the innermost widget at a position, within a root widget.
|
||||||
|
-- Should always return a widget since all positions are within
|
||||||
|
-- the layout's root widget.
|
||||||
|
function Layout:getWidgetAt (x, y, root)
|
||||||
|
local widget = root or self.root
|
||||||
|
local children = widget.children
|
||||||
|
local childCount = #children
|
||||||
|
-- Loop through in reverse, because siblings defined later in the tree
|
||||||
|
-- will overdraw earlier siblings.
|
||||||
|
for i = childCount, 1, -1 do
|
||||||
|
local child = children[i]
|
||||||
|
local inner = self:getWidgetAt(x, y, child)
|
||||||
|
if inner then return inner end
|
||||||
|
end
|
||||||
|
if widget:isAt(x, y) then return widget end
|
||||||
|
if widget == self.root then return widget end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Internal, called from Widget:new
|
||||||
|
function Layout:addWidget (widget)
|
||||||
|
if widget.id then
|
||||||
|
self[widget.id] = widget
|
||||||
|
end
|
||||||
|
table.insert(self.widgets, widget)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- event binders
|
||||||
|
|
||||||
|
Event.injectBinders(Layout)
|
||||||
|
|
||||||
|
return Layout
|
||||||
144
luigi/renderer.lua
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
local Event = require(ROOT .. 'event')
|
||||||
|
local Font = require(ROOT .. 'font')
|
||||||
|
|
||||||
|
local Renderer = Base:extend()
|
||||||
|
|
||||||
|
function Renderer:renderBackground (widget, window)
|
||||||
|
local bg = widget.background
|
||||||
|
if not bg then return end
|
||||||
|
local bend = widget.bend
|
||||||
|
local x1, y1, x2, y2 = widget:getRectangle(true)
|
||||||
|
window:fill(x1, y1, x2, y2, bg, bend)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Renderer:renderOutline (widget, window)
|
||||||
|
if not widget.outline then return end
|
||||||
|
local x1, y1, x2, y2 = widget:getRectangle(true)
|
||||||
|
window:outline(x1, y1, x2, y2, widget.outline)
|
||||||
|
end
|
||||||
|
|
||||||
|
local imageCache = {}
|
||||||
|
|
||||||
|
local function loadImage (path)
|
||||||
|
if not imageCache[path] then
|
||||||
|
imageCache[path] = love.graphics.newImage(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
return imageCache[path]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: this function is a monster, fix it somehow
|
||||||
|
function Renderer:renderIconAndText (widget, window)
|
||||||
|
local x1, y1, x2, y2 = widget:getRectangle(true, true)
|
||||||
|
local icon = widget.icon and loadImage(widget.icon)
|
||||||
|
local align = widget.align or ''
|
||||||
|
local padding = widget.padding or 0
|
||||||
|
local text = widget.text
|
||||||
|
local x, y, iconWidth, iconHeight
|
||||||
|
|
||||||
|
if icon then
|
||||||
|
iconWidth, iconHeight = icon:getWidth(), icon:getHeight()
|
||||||
|
-- horizontal alignment
|
||||||
|
if align:find('right') then
|
||||||
|
x = x2 - iconWidth
|
||||||
|
elseif align:find('center') then
|
||||||
|
x = x1 + (x2 - x1) / 2 - iconWidth / 2
|
||||||
|
else -- if align:find('left') then
|
||||||
|
x = x1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- vertical alignment
|
||||||
|
if align:find('bottom') then
|
||||||
|
y = y2 - iconHeight
|
||||||
|
elseif align:find('middle') then
|
||||||
|
y = y1 + (y2 - y1) / 2 - iconHeight / 2
|
||||||
|
else -- if align:find('top') then
|
||||||
|
y = y1
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
if text and align:find('center') then
|
||||||
|
if align:find('bottom') then
|
||||||
|
y = y - textHeight - padding
|
||||||
|
elseif align:find('middle') then
|
||||||
|
y = y - (textHeight + padding) / 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
love.graphics.draw(icon, x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- render text
|
||||||
|
if not text then return end
|
||||||
|
|
||||||
|
if not widget.fontData then
|
||||||
|
widget.fontData = Font(widget.font, widget.fontSize, widget.textColor)
|
||||||
|
end
|
||||||
|
|
||||||
|
local font = widget.fontData
|
||||||
|
|
||||||
|
if icon then
|
||||||
|
if align:find('center') then
|
||||||
|
-- y1 = y1 + iconHeight + padding
|
||||||
|
elseif align:find('right') then
|
||||||
|
x2 = x2 - iconWidth - padding
|
||||||
|
else
|
||||||
|
x1 = x1 + iconWidth + padding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
font:setWidth(x2 - x1)
|
||||||
|
if align:find('right') then
|
||||||
|
font:setAlignment('right')
|
||||||
|
elseif align:find('center') then
|
||||||
|
font:setAlignment('center')
|
||||||
|
elseif align:find('justify') then
|
||||||
|
font:setAlignment('justify')
|
||||||
|
else -- if align:find('left') then
|
||||||
|
font:setAlignment('left')
|
||||||
|
end
|
||||||
|
|
||||||
|
local textHeight = font:getWrappedHeight(text)
|
||||||
|
|
||||||
|
local x, y
|
||||||
|
|
||||||
|
-- vertical alignment
|
||||||
|
if align:find('bottom') then
|
||||||
|
y = y2 - textHeight
|
||||||
|
elseif align:find('middle') then
|
||||||
|
y = y2 - (y2 - y1) / 2 - textHeight / 2
|
||||||
|
if icon and align:find('center') then
|
||||||
|
y = y1 + (iconHeight + padding) / 2
|
||||||
|
end
|
||||||
|
else -- if align:find('top') then
|
||||||
|
y = y1
|
||||||
|
if icon and align:find('center') then
|
||||||
|
y = y1 + iconHeight + padding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
x = math.floor(x1)
|
||||||
|
y = math.floor(y)
|
||||||
|
|
||||||
|
window:write(x, y, x1, y1, x2, y2, text, font)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Renderer:renderChildren (widget)
|
||||||
|
for i, child in ipairs(widget.children) do self:render(child) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Renderer:render (widget)
|
||||||
|
Event.Display:emit(widget, {}, function()
|
||||||
|
local window = widget.layout.window
|
||||||
|
self:renderBackground(widget, window)
|
||||||
|
self:renderOutline(widget, window)
|
||||||
|
self:renderIconAndText(widget, window)
|
||||||
|
return self:renderChildren(widget)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Renderer
|
||||||
79
luigi/style.lua
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
|
||||||
|
local Style = Base:extend()
|
||||||
|
|
||||||
|
function Style:constructor (rules, ...)
|
||||||
|
self.rules = rules
|
||||||
|
self.lookupNames = { ... }
|
||||||
|
end
|
||||||
|
|
||||||
|
function Style:getProperty (object, property)
|
||||||
|
local ownProperty = rawget(object, property)
|
||||||
|
if ownProperty ~= nil then return ownProperty end
|
||||||
|
for styleDef in self:each(object) do
|
||||||
|
local result = self:getProperty(styleDef, property)
|
||||||
|
if result ~= nil then return result end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Style:each (object)
|
||||||
|
local rules = self.rules
|
||||||
|
local nextStyleName = self:eachName(object)
|
||||||
|
return function ()
|
||||||
|
local styleName = nextStyleName()
|
||||||
|
while styleName do
|
||||||
|
local styleDef = rules[styleName]
|
||||||
|
if styleDef then return styleDef end
|
||||||
|
styleName = nextStyleName()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Style:eachName (object)
|
||||||
|
local lookupNames = self.lookupNames
|
||||||
|
local lookupNameIndex = 0
|
||||||
|
local lookupPropIndex = 0
|
||||||
|
local lookupProp
|
||||||
|
|
||||||
|
local returnedSpecialName = {}
|
||||||
|
|
||||||
|
local function checkLookupProp()
|
||||||
|
if type(lookupProp) == 'table' and lookupPropIndex >= #lookupProp then
|
||||||
|
lookupProp = nil
|
||||||
|
end
|
||||||
|
while not lookupProp do
|
||||||
|
returnedSpecialName = {}
|
||||||
|
lookupPropIndex = 0
|
||||||
|
lookupNameIndex = lookupNameIndex + 1
|
||||||
|
if lookupNameIndex > #lookupNames then return end
|
||||||
|
lookupProp = rawget(object, lookupNames[lookupNameIndex])
|
||||||
|
if type(lookupProp) == 'string' then
|
||||||
|
lookupProp = { lookupProp }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local function getSpecialName (...)
|
||||||
|
for k, name in ipairs({ ... }) do
|
||||||
|
if not returnedSpecialName[name] then
|
||||||
|
returnedSpecialName[name] = true
|
||||||
|
if rawget(object, name) then
|
||||||
|
return lookupProp[lookupPropIndex + 1] .. '_' .. name
|
||||||
|
else
|
||||||
|
return lookupProp[lookupPropIndex + 1] .. '_not_' .. name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return function ()
|
||||||
|
if not checkLookupProp() then return end
|
||||||
|
local specialName = getSpecialName('pressed', 'hovered')
|
||||||
|
if specialName then return specialName end
|
||||||
|
lookupPropIndex = lookupPropIndex + 1
|
||||||
|
return lookupProp[lookupPropIndex]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Style
|
||||||
39
luigi/theme/light.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
local backColor = { 240, 240, 240 }
|
||||||
|
local lineColor = { 220, 220, 220 }
|
||||||
|
local highlightColor = { 220, 220, 240 }
|
||||||
|
|
||||||
|
return {
|
||||||
|
panel = {
|
||||||
|
background = backColor,
|
||||||
|
padding = 4,
|
||||||
|
},
|
||||||
|
button = {
|
||||||
|
type = 'panel',
|
||||||
|
align = 'center middle',
|
||||||
|
outline = lineColor,
|
||||||
|
bend = 0.1,
|
||||||
|
margin = 4,
|
||||||
|
},
|
||||||
|
button_hovered = {
|
||||||
|
bend = 0.2,
|
||||||
|
},
|
||||||
|
button_pressed = {
|
||||||
|
bend = -0.1,
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
align = 'left middle',
|
||||||
|
background = { 255, 255, 255 },
|
||||||
|
outline = lineColor,
|
||||||
|
bend = -0.1,
|
||||||
|
margin = 4,
|
||||||
|
padding = 4,
|
||||||
|
},
|
||||||
|
sash = {
|
||||||
|
background = highlightColor
|
||||||
|
},
|
||||||
|
slider = {
|
||||||
|
type = 'panel',
|
||||||
|
outline = lineColor,
|
||||||
|
bend = 0.1,
|
||||||
|
},
|
||||||
|
}
|
||||||
246
luigi/widget.lua
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
local Event = require(ROOT .. 'event')
|
||||||
|
|
||||||
|
local Widget = Base:extend()
|
||||||
|
|
||||||
|
Widget.isWidget = true
|
||||||
|
|
||||||
|
Widget.registeredTypes = {
|
||||||
|
sash = ROOT .. 'widget.sash',
|
||||||
|
slider = ROOT .. 'widget.slider',
|
||||||
|
text = ROOT .. 'widget.text',
|
||||||
|
}
|
||||||
|
|
||||||
|
function Widget.create (layout, data)
|
||||||
|
local path = data.type and Widget.registeredTypes[data.type]
|
||||||
|
|
||||||
|
if path then
|
||||||
|
return require(path)(layout, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Widget(layout, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:constructor (layout, data)
|
||||||
|
self.type = 'generic'
|
||||||
|
self.layout = layout
|
||||||
|
self.children = {}
|
||||||
|
self.position = { x = nil, y = nil }
|
||||||
|
self.dimensions = { width = nil, height = nil }
|
||||||
|
self:extract(data)
|
||||||
|
layout:addWidget(self)
|
||||||
|
local widget = self
|
||||||
|
local meta = getmetatable(self)
|
||||||
|
local metaIndex = meta.__index
|
||||||
|
function meta:__index(property)
|
||||||
|
local value = metaIndex[property]
|
||||||
|
local style = widget.layout.style
|
||||||
|
local theme = widget.layout.theme
|
||||||
|
if value ~= nil then return value end
|
||||||
|
value = style and style:getProperty(self, property)
|
||||||
|
if value ~= nil then return value end
|
||||||
|
return theme and theme:getProperty(self, property)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:extract (data)
|
||||||
|
function toWidget(t)
|
||||||
|
if t.isWidget then return t end
|
||||||
|
return Widget.create(self.layout, t)
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(data) do
|
||||||
|
if type(k) == 'number' then
|
||||||
|
self.children[k] = toWidget(v)
|
||||||
|
self.children[k].parent = self
|
||||||
|
else
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getPrevious ()
|
||||||
|
local siblings = self.parent.children
|
||||||
|
for i, widget in ipairs(siblings) do
|
||||||
|
if widget == self then return siblings[i - 1] end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getNext ()
|
||||||
|
local siblings = self.parent.children
|
||||||
|
for i, widget in ipairs(siblings) do
|
||||||
|
if widget == self then return siblings[i + 1] end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:addChild (data)
|
||||||
|
local layout = self.layout
|
||||||
|
local child = Widget.create(layout, data)
|
||||||
|
|
||||||
|
table.insert(self.children, child)
|
||||||
|
child.parent = self
|
||||||
|
layout:addWidget(child)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:calculateDimension (name)
|
||||||
|
function clamp(value, min, max)
|
||||||
|
if value < min then
|
||||||
|
value = min
|
||||||
|
elseif value > max then
|
||||||
|
value = max
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
if self[name] then
|
||||||
|
self.dimensions[name] = clamp(self[name], 0, self.layout.root[name])
|
||||||
|
end
|
||||||
|
if self.dimensions[name] then
|
||||||
|
return self.dimensions[name]
|
||||||
|
end
|
||||||
|
local parent = self.parent
|
||||||
|
if not parent then
|
||||||
|
return self.layout
|
||||||
|
end
|
||||||
|
local parentDimension = parent:calculateDimension(name)
|
||||||
|
local parentFlow = parent.flow or 'y'
|
||||||
|
if (parentFlow == 'y' and name == 'width') or
|
||||||
|
(parentFlow == 'x' and name == 'height')
|
||||||
|
then
|
||||||
|
return parentDimension
|
||||||
|
end
|
||||||
|
local claimed = 0
|
||||||
|
local unsized = 1
|
||||||
|
for i, widget in ipairs(self.parent.children) do
|
||||||
|
if widget ~= self then
|
||||||
|
if widget[name] then
|
||||||
|
claimed = claimed + widget:calculateDimension(name)
|
||||||
|
if claimed > parentDimension then
|
||||||
|
claimed = parentDimension
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unsized = unsized + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local size = (self.parent:calculateDimension(name) - claimed) / unsized
|
||||||
|
self.dimensions[name] = clamp(size, 0, self.layout.root[name])
|
||||||
|
return size
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:calculatePosition (axis)
|
||||||
|
if self.position[axis] then
|
||||||
|
return self.position[axis]
|
||||||
|
end
|
||||||
|
local parent = self.parent
|
||||||
|
if not parent then
|
||||||
|
self.position[axis] = 0
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local parentPos = parent:calculatePosition(axis)
|
||||||
|
local p = parentPos
|
||||||
|
local parentFlow = parent.flow or 'y'
|
||||||
|
for i, widget in ipairs(parent.children) do
|
||||||
|
if widget == self then
|
||||||
|
self.position[axis] = p
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
if parentFlow == axis then
|
||||||
|
local dimension = (axis == 'x') and 'width' or 'height'
|
||||||
|
p = p + widget:calculateDimension(dimension)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.position[axis] = 0
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getX ()
|
||||||
|
return self:calculatePosition('x')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getY ()
|
||||||
|
return self:calculatePosition('y')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getWidth ()
|
||||||
|
return self:calculateDimension('width')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getHeight ()
|
||||||
|
return self:calculateDimension('height')
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:setDimension (name, size)
|
||||||
|
local parentDimension = self.parent:calculateDimension(name)
|
||||||
|
local claimed = 0
|
||||||
|
for i, widget in ipairs(self.parent.children) do
|
||||||
|
if widget ~= self and widget[name] then
|
||||||
|
claimed = claimed + widget[name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if claimed + size > parentDimension then
|
||||||
|
size = parentDimension - claimed
|
||||||
|
end
|
||||||
|
self[name] = size
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:setWidth (size)
|
||||||
|
return self:setDimension('width', size)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:setHeight (size)
|
||||||
|
return self:setDimension('height', size)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getOrigin ()
|
||||||
|
return self:getX(), self:getY()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getExtent ()
|
||||||
|
local x, y = self:getX(), self:getY()
|
||||||
|
return x + self:getWidth(), y + self:getHeight()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getRectangle (useMargin, usePadding)
|
||||||
|
local x1, y1 = self:getOrigin()
|
||||||
|
local x2, y2 = self:getExtent()
|
||||||
|
local function shrink(amount)
|
||||||
|
x1 = x1 + amount
|
||||||
|
y1 = y1 + amount
|
||||||
|
x2 = x2 - amount
|
||||||
|
y2 = y2 - amount
|
||||||
|
end
|
||||||
|
if useMargin then
|
||||||
|
shrink(self.margin or 0)
|
||||||
|
end
|
||||||
|
if usePadding then
|
||||||
|
shrink(self.padding or 0)
|
||||||
|
end
|
||||||
|
return x1, y1, x2, y2
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:isAt (x, y)
|
||||||
|
local x1, y1, x2, y2 = self:getRectangle()
|
||||||
|
return (x1 < x) and (x2 > x) and (y1 < y) and (y2 > y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:getAncestors (includeSelf)
|
||||||
|
local instance = includeSelf and self or self.parent
|
||||||
|
return function()
|
||||||
|
local widget = instance
|
||||||
|
if not widget then return end
|
||||||
|
instance = widget.parent
|
||||||
|
return widget
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Widget:update ()
|
||||||
|
self.layout:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- event binders
|
||||||
|
|
||||||
|
Event.injectBinders(Widget)
|
||||||
|
|
||||||
|
return Widget
|
||||||
35
luigi/widget/sash.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local Widget = require((...):gsub('%.[^.]*$', ''))
|
||||||
|
|
||||||
|
local Sash = Widget:extend()
|
||||||
|
|
||||||
|
function Sash:constructor(layout, data)
|
||||||
|
Widget.constructor(self, layout, data)
|
||||||
|
|
||||||
|
self:onPressDrag(function(event)
|
||||||
|
local axis = self.parent.flow
|
||||||
|
if axis == 'x' then
|
||||||
|
dimension = 'width'
|
||||||
|
else
|
||||||
|
axis = 'y'
|
||||||
|
dimension = 'height'
|
||||||
|
end
|
||||||
|
local prevSibling = self:getPrevious()
|
||||||
|
local nextSibling = self:getNext()
|
||||||
|
local prevSize = prevSibling and prevSibling[dimension]
|
||||||
|
local nextSize = nextSibling and nextSibling[dimension]
|
||||||
|
if prevSize then
|
||||||
|
prevSibling:setDimension(dimension,
|
||||||
|
event[axis] - prevSibling:calculatePosition(axis))
|
||||||
|
end
|
||||||
|
if nextSize then
|
||||||
|
nextSibling:setDimension(dimension,
|
||||||
|
nextSibling:calculatePosition(axis) +
|
||||||
|
nextSibling[dimension] - event[axis])
|
||||||
|
end
|
||||||
|
|
||||||
|
layout:update(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return Sash
|
||||||
52
luigi/widget/slider.lua
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
local Widget = require((...):gsub('%.[^.]*$', ''))
|
||||||
|
|
||||||
|
local Slider = Widget:extend()
|
||||||
|
|
||||||
|
function Slider:constructor(layout, data)
|
||||||
|
Widget.constructor(self, layout, data)
|
||||||
|
|
||||||
|
local function getCenter()
|
||||||
|
return self:getX() + self:getWidth() / 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local position = 0.5
|
||||||
|
|
||||||
|
self:onPressDrag(function(event)
|
||||||
|
local x1, y1, x2, y2 = self:getRectangle(true, true)
|
||||||
|
position = (event.x - x1) / (x2 - x1)
|
||||||
|
if position < 0 then position = 0 end
|
||||||
|
if position > 1 then position = 1 end
|
||||||
|
self:update()
|
||||||
|
end)
|
||||||
|
|
||||||
|
self:onDisplay(function(event)
|
||||||
|
-- event:yield()
|
||||||
|
local x1, y1, x2, y2 = self:getRectangle(true, true)
|
||||||
|
local padding = self.padding or 0
|
||||||
|
self.layout.window:fill(
|
||||||
|
x1,
|
||||||
|
y1 + (y2 - y1) / 2 - padding / 2,
|
||||||
|
x2,
|
||||||
|
y1 + (y2 - y1) / 2 + padding / 2,
|
||||||
|
self.background, -(self.bend or 0)
|
||||||
|
)
|
||||||
|
self.layout.window:fill(
|
||||||
|
x1 + position * (x2 - x1) - padding,
|
||||||
|
y1 + padding,
|
||||||
|
x1 + position * (x2 - x1) + padding,
|
||||||
|
y2 - padding,
|
||||||
|
self.background, self.bend
|
||||||
|
)
|
||||||
|
self.layout.window:outline(
|
||||||
|
x1 + position * (x2 - x1) - padding,
|
||||||
|
y1 + padding,
|
||||||
|
x1 + position * (x2 - x1) + padding,
|
||||||
|
y2 - padding,
|
||||||
|
self.outline
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
end)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return Slider
|
||||||
9
luigi/widget/text.lua
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
local Widget = require((...):gsub('%.[^.]*$', ''))
|
||||||
|
|
||||||
|
local Text = Widget:extend()
|
||||||
|
|
||||||
|
function Text:constructor(layout, data)
|
||||||
|
Widget.constructor(self, layout, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Text
|
||||||
125
luigi/window.lua
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
local ROOT = (...):gsub('[^.]*$', '')
|
||||||
|
|
||||||
|
local Base = require(ROOT .. 'base')
|
||||||
|
local Hooker = require(ROOT .. 'hooker')
|
||||||
|
|
||||||
|
local Window = Base:extend()
|
||||||
|
|
||||||
|
local unpack = table.unpack or _G.unpack
|
||||||
|
|
||||||
|
function Window:constructor (input)
|
||||||
|
self.input = input
|
||||||
|
self.isMousePressed = false
|
||||||
|
self.isManagingInput = false
|
||||||
|
self.hooked = {}
|
||||||
|
self.hooks = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:hook (key, method)
|
||||||
|
self.hooks[#self.hooks + 1] = Hooker.hook(key, method)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:unhook ()
|
||||||
|
for _, item in ipairs(self.hooks) do
|
||||||
|
Hooker.unhook(item)
|
||||||
|
end
|
||||||
|
self.hooks = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:manageInput (input)
|
||||||
|
if self.isManagingInput then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.isManagingInput = true
|
||||||
|
|
||||||
|
self:hook('draw', function ()
|
||||||
|
input:handleDisplay()
|
||||||
|
end)
|
||||||
|
self:hook('resize', function (width, height)
|
||||||
|
return input:handleReshape(width, height)
|
||||||
|
end)
|
||||||
|
self:hook('mousepressed', function (x, y, button)
|
||||||
|
self.isMousePressed = true
|
||||||
|
return input:handlePressStart(button, x, y)
|
||||||
|
end)
|
||||||
|
self:hook('mousereleased', function (x, y, button)
|
||||||
|
self.isMousePressed = false
|
||||||
|
return input:handlePressEnd(button, x, y)
|
||||||
|
end)
|
||||||
|
self:hook('mousemoved', function (x, y, dx, dy)
|
||||||
|
if self.isMousePressed then
|
||||||
|
return input:handlePressedMotion(x, y)
|
||||||
|
else
|
||||||
|
return input:handleMotion(x, y)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self:hook('keypressed', function (key, isRepeat)
|
||||||
|
return input:handleKeyboard(key, love.mouse.getX(), love.mouse.getY())
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:show (width, height, title)
|
||||||
|
local currentWidth, currentHeight, flags = love.window.getMode()
|
||||||
|
love.window.setMode(width or currentWidth, height or currentHeight, flags)
|
||||||
|
if title then
|
||||||
|
love.window.setTitle(title)
|
||||||
|
end
|
||||||
|
self:manageInput(self.input)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:hide ()
|
||||||
|
if not self.isManagingInput then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.isManagingInput = false
|
||||||
|
self:unhook()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setColor (color)
|
||||||
|
love.graphics.setColor(color)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:fill (x1, y1, x2, y2, color)
|
||||||
|
setColor(color)
|
||||||
|
love.graphics.rectangle('fill', x1, y1, x2 - x1, y2 - y1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:outline (x1, y1, x2, y2, color)
|
||||||
|
setColor(color)
|
||||||
|
love.graphics.rectangle('line', x1, y1, x2 - x1, y2 - y1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:write (x, y, x1, y1, x2, y2, text, font)
|
||||||
|
|
||||||
|
local width, height = x2 - x1, y2 - y1
|
||||||
|
|
||||||
|
if width < 1 or height < 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local sx, sy, sw, sh = love.graphics.getScissor()
|
||||||
|
|
||||||
|
love.graphics.setScissor(x1, y1, width, height)
|
||||||
|
local oldFont = love.graphics.getFont()
|
||||||
|
love.graphics.setFont(font.font)
|
||||||
|
|
||||||
|
setColor(font.color)
|
||||||
|
|
||||||
|
local layout = font.layout
|
||||||
|
love.graphics.printf(text, x, y, layout.width or width, layout.align)
|
||||||
|
|
||||||
|
love.graphics.setScissor(sx, sy, sw, sh)
|
||||||
|
love.graphics.setFont(oldFont)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Window:update (reshape)
|
||||||
|
if reshape then
|
||||||
|
for i, widget in ipairs(self.input.layout.widgets) do
|
||||||
|
widget.position = {}
|
||||||
|
widget.dimensions = {}
|
||||||
|
widget.fontData = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Window
|
||||||