initial commit

This commit is contained in:
airstruck
2015-10-21 18:35:14 -04:00
commit e490e2899f
52 changed files with 1506 additions and 0 deletions

3
example/conf.lua Normal file
View File

@@ -0,0 +1,3 @@
function love.conf (t)
t.window.resizable = true
end

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

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

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
example/icon/emblem-new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
example/icon/emblem-web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

1
example/luigi Symbolic link
View File

@@ -0,0 +1 @@
../luigi/

124
example/main.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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