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
|
||||