From 4638fb1ea53c70e87610755775291dd58d4fb82b Mon Sep 17 00:00:00 2001 From: endlesstravel Date: Sun, 6 Nov 2016 15:06:58 +0800 Subject: [PATCH 1/3] add ime support --- README.md | 17 ++++++++++-- core.lua | 21 +++++++++++++- docs/core.rst | 76 ++++++++++++++++++++++++++++++++------------------- init.lua | 7 ++++- input.lua | 4 +-- layout.lua | 49 +++++++++++++++++++++++++++++++++ theme.lua | 24 ++++++++++++++-- 7 files changed, 160 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b900540..7db2ffa 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,13 @@ More info and code is over at [readthedocs](http://suit.readthedocs.org/en/lates local suit = require 'suit' -- storage for text input -local input = {text = ""} +local input = {text = "", candidate_text = {text="", start=0, length=0}} + +-- make love use font which support CJK text +function love.load() + local font = love.graphics.newFont("NotoSansHans-Regular.otf", 20) + love.graphics.setFont(font) +end -- all the UI is defined in love.update or functions that are called from here function love.update(dt) @@ -33,7 +39,7 @@ function love.update(dt) -- put an input widget at the layout origin, with a cell size of 200 by 30 pixels suit.Input(input, suit.layout:row(200,30)) - + -- put a label that displays the text below the first cell -- the cell size is the same as the last one (200x30 px) -- the label text will be aligned to the left @@ -41,7 +47,7 @@ function love.update(dt) -- put an empty cell that has the same size as the last cell (200x30 px) suit.layout:row() - + -- put a button of size 200x30 px in the cell below -- if the button is pressed, quit the game if suit.Button("Close", suit.layout:row()).hit then @@ -54,6 +60,11 @@ function love.draw() suit.draw() end +function love.textedited(text, start, length) + -- for IME input + input.candidate_text = {text = text, start= start, length = length} +end + function love.textinput(t) -- forward text input to SUIT suit.textinput(t) diff --git a/core.lua b/core.lua index cc25b44..6c32fbd 100644 --- a/core.lua +++ b/core.lua @@ -48,15 +48,29 @@ function suit:wasHovered(id) return id == self.hovered_last end +function suit:anyActive() + return self.active ~= nil +end + function suit:isActive(id) return id == self.active end +function suit:anyHit() + return self.hit ~= nil +end + +function suit:isHit(id) + return id == self.hit +end + function suit:getStateName(id) if self:isActive(id) then return "active" elseif self:isHovered(id) then return "hovered" + elseif self:isHit(id) then + return "hit" end return "normal" end @@ -84,7 +98,11 @@ function suit:registerHitbox(id, x,y,w,h) end function suit:mouseReleasedOn(id) - return not self.mouse_button_down and self:isActive(id) and self:isHovered(id) + if not self.mouse_button_down and self:isActive(id) and self:isHovered(id) then + self.hit = id + return true + end + return false end function suit:updateMouse(x, y, button_down) @@ -145,6 +163,7 @@ function suit:enterFrame() self:updateMouse(love.mouse.getX(), love.mouse.getY(), love.mouse.isDown(1)) self.key_down, self.textchar = nil, "" self:grabKeyboardFocus(NONE) + self.hit = nil end function suit:exitFrame() diff --git a/docs/core.rst b/docs/core.rst index d2f7622..731c95e 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -46,33 +46,6 @@ Forwards a ``love.keypressed(key)`` event to SUIT. Forwards a ``love.textinput(key)`` event to SUIT. -Internal Helpers ----------------- - -.. function:: getOptionsAndSize(...) - - :param mixed ...: Varargs. - :returns: ``options, x,y,w,h``. - -Converts varargs to option table and size definition. Used in the widget -functions. - -.. function:: registerDraw(f, ...) - - :param function f: Function to call in ``draw()``. - :param mixed ...: Arguments to f. - -Registers a function to be executed during :func:`draw()`. Used by widgets to -make themselves visible. - -.. function:: enterFrame() - -Prepares GUI state when entering a frame. - -.. function:: exitFrame() - -Clears GUI state when exiting a frame. - GUI State ^^^^^^^^^ @@ -96,12 +69,59 @@ Checks if the widget identified by ``id`` is hovered by the mouse. Checks if the widget identified by ``id`` was hovered by the mouse in the last frame. +.. function:: anyActive() + + :returns: ``true`` if any widget is in the ``active`` state. + +Checks whether the mouse button is pressed and held on any widget. + .. function:: isActive(id) :param mixed id: Identifier of the widget. :returns: ``true`` if the widget is in the ``active`` state. -Checks whether the mouse button is pressed on the widget identified by ``id``. +Checks whether the mouse button is pressed and held on the widget identified by ``id``. + +.. function:: anyHit() + + :returns: ``true`` if the mouse was pressed and released on any widget. + +Check whether the mouse was pressed and released on any widget. + +.. function:: isHit(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the mouse was pressed and released on the widget. + +Check whether the mouse was pressed and released on the widget identified by ``id``. + + +Internal Helpers +---------------- + +.. function:: getOptionsAndSize(...) + + :param mixed ...: Varargs. + :returns: ``options, x,y,w,h``. + +Converts varargs to option table and size definition. Used in the widget +functions. + +.. function:: registerDraw(f, ...) + + :param function f: Function to call in ``draw()``. + :param mixed ...: Arguments to f. + +Registers a function to be executed during :func:`draw()`. Used by widgets to +make themselves visible. + +.. function:: enterFrame() + +Prepares GUI state when entering a frame. + +.. function:: exitFrame() + +Clears GUI state when exiting a frame. Mouse Input diff --git a/init.lua b/init.lua index 9542735..511cdfa 100644 --- a/init.lua +++ b/init.lua @@ -5,6 +5,8 @@ local suit = require(BASE .. "core") local instance = suit.new() return setmetatable({ + _instance = instance, + new = suit.new, getOptionsAndSize = suit.getOptionsAndSize, @@ -12,7 +14,10 @@ return setmetatable({ anyHovered = function(...) return instance:anyHovered(...) end, isHovered = function(...) return instance:isHovered(...) end, wasHovered = function(...) return instance:wasHovered(...) end, + anyActive = function(...) return instance:anyActive(...) end, isActive = function(...) return instance:isActive(...) end, + anyHit = function(...) return instance:anyHit(...) end, + isHit = function(...) return instance:isHit(...) end, mouseInRect = function(...) return instance:mouseInRect(...) end, registerHitbox = function(...) return instance:registerHitbox(...) end, @@ -49,7 +54,7 @@ return setmetatable({ if k == "theme" then instance.theme = v else - rawset(t, k, v) + rawset(instance, k, v) end end, __index = function(t, k) diff --git a/input.lua b/input.lua index 8664d1c..cab9522 100644 --- a/input.lua +++ b/input.lua @@ -52,10 +52,10 @@ return function(core, input, ...) opt.state = core:registerHitbox(opt.id, x,y,w,h) opt.hasKeyboardFocus = core:grabKeyboardFocus(opt.id) - if opt.hasKeyboardFocus then + if (input.candidate_text.text == "") and opt.hasKeyboardFocus then local keycode,char = core:getPressedKey() -- text input - if char ~= "" then + if char and char ~= "" then local a,b = split(input.text, input.cursor) input.text = table.concat{a, char, b} input.cursor = input.cursor + utf8.len(char) diff --git a/layout.lua b/layout.lua index 82c3b5f..58c7034 100644 --- a/layout.lua +++ b/layout.lua @@ -35,10 +35,22 @@ function Layout:nextRow() return self._x, self._y + self._h + self._pady end +Layout.nextDown = Layout.nextRow + +function Layout:nextUp() + return self._x, self._y - self._h - self._pady +end + function Layout:nextCol() return self._x + self._w + self._padx, self._y end +Layout.nextRight = Layout.nextCol + +function Layout:nextLeft() + return self._x - self._w - self._padx, self._y +end + function Layout:push(x,y) self._stack[#self._stack+1] = { self._x, self._y, @@ -59,6 +71,7 @@ function Layout:pop() self._w, self._h, self._widths, self._heights = unpack(self._stack[#self._stack]) self._isFirstCell = false + self._stack[#self._stack] = nil self._w, self._h = math.max(w, self._w or 0), math.max(h, self._h or 0) @@ -135,6 +148,22 @@ function Layout:row(w, h) return x,y,w,h end +Layout.down = Layout.row + +function Layout:up(w, h) + w,h = calc_width_height(self, w, h) + local x,y = self._x, self._y - (self._h or 0) + + if not self._isFirstCell then + y = y - self._pady + end + self._isFirstCell = false + + self._y, self._w, self._h = y, w, h + + return x,y,w,h +end + function Layout:col(w, h) w,h = calc_width_height(self, w, h) @@ -150,6 +179,22 @@ function Layout:col(w, h) return x,y,w,h end +Layout.right = Layout.col + +function Layout:left(w, h) + w,h = calc_width_height(self, w, h) + + local x,y = self._x - (self._w or 0), self._y + + if not self._isFirstCell then + x = x - self._padx + end + self._isFirstCell = false + + self._x, self._w, self._h = x, w, h + + return x,y,w,h +end local function layout_iterator(t, idx) idx = (idx or 1) + 1 @@ -323,6 +368,10 @@ return setmetatable({ pop = function(...) return instance:pop(...) end, row = function(...) return instance:row(...) end, col = function(...) return instance:col(...) end, + down = function(...) return instance:down(...) end, + up = function(...) return instance:up(...) end, + left = function(...) return instance:left(...) end, + right = function(...) return instance:right(...) end, rows = function(...) return instance:rows(...) end, cols = function(...) return instance:cols(...) end, }, {__call = function(_,...) return Layout.new(...) end}) diff --git a/theme.lua b/theme.lua index d0dd376..53bdbf4 100644 --- a/theme.lua +++ b/theme.lua @@ -21,6 +21,10 @@ end function theme.drawBox(x,y,w,h, colors, cornerRadius) local colors = colors or theme.getColorForState(opt) cornerRadius = cornerRadius or theme.cornerRadius + w = math.max(cornerRadius/2, w) + if h < cornerRadius/2 then + y,h = y - (cornerRadius - h), cornerRadius/2 + end love.graphics.setColor(colors.bg) love.graphics.rectangle('fill', x,y, w,h, cornerRadius) @@ -89,7 +93,7 @@ function theme.Slider(fraction, opt, x,y,w,h) local c = theme.getColorForState(opt) theme.drawBox(x,y,w,h, c, opt.cornerRadius) - theme.drawBox(x,yb,wb,hb, {bg=c.fg}, opt.cornerRadius) + theme.drawBox(xb,yb,wb,hb, {bg=c.fg}, opt.cornerRadius) if opt.state ~= nil and opt.state ~= "normal" then love.graphics.setColor((opt.color and opt.color.active or {}).fg or theme.color.active.fg) @@ -119,12 +123,26 @@ function theme.Input(input, opt, x,y,w,h) love.graphics.setFont(opt.font) love.graphics.print(input.text, x, y+(h-th)/2) + -- candidate text + local tw = opt.font:getWidth(input.text) + local ctw = opt.font:getWidth(input.candidate_text.text) + love.graphics.setColor((opt.color and opt.color.normal and opt.color.normal.fg) or theme.color.normal.fg) + love.graphics.print(input.candidate_text.text, x + tw, y+(h-th)/2) + + -- candidate text rectangle box + love.graphics.rectangle("line", x + tw, y+(h-th)/2, ctw, th) + -- cursor if opt.hasKeyboardFocus and (love.timer.getTime() % 1) > .5 then + local ct = input.candidate_text; + local ss = ct.text:sub(1, utf8.offset(ct.text, ct.start)) + local ws = opt.font:getWidth(ss) + if ct.start == 0 then ws = 0 end + love.graphics.setLineWidth(1) love.graphics.setLineStyle('rough') - love.graphics.line(x + opt.cursor_pos, y + (h-th)/2, - x + opt.cursor_pos, y + (h+th)/2) + love.graphics.line(x + opt.cursor_pos + ws, y + (h-th)/2, + x + opt.cursor_pos + ws, y + (h+th)/2) end -- reset scissor From 10e79ca5c8a14651547a1340909e95f0cc1649a1 Mon Sep 17 00:00:00 2001 From: endlesstravel Date: Sun, 6 Nov 2016 15:18:02 +0800 Subject: [PATCH 2/3] update to master --- README.md | 17 +++-------------- input.lua | 2 +- theme.lua | 18 ++---------------- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7db2ffa..b900540 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,7 @@ More info and code is over at [readthedocs](http://suit.readthedocs.org/en/lates local suit = require 'suit' -- storage for text input -local input = {text = "", candidate_text = {text="", start=0, length=0}} - --- make love use font which support CJK text -function love.load() - local font = love.graphics.newFont("NotoSansHans-Regular.otf", 20) - love.graphics.setFont(font) -end +local input = {text = ""} -- all the UI is defined in love.update or functions that are called from here function love.update(dt) @@ -39,7 +33,7 @@ function love.update(dt) -- put an input widget at the layout origin, with a cell size of 200 by 30 pixels suit.Input(input, suit.layout:row(200,30)) - + -- put a label that displays the text below the first cell -- the cell size is the same as the last one (200x30 px) -- the label text will be aligned to the left @@ -47,7 +41,7 @@ function love.update(dt) -- put an empty cell that has the same size as the last cell (200x30 px) suit.layout:row() - + -- put a button of size 200x30 px in the cell below -- if the button is pressed, quit the game if suit.Button("Close", suit.layout:row()).hit then @@ -60,11 +54,6 @@ function love.draw() suit.draw() end -function love.textedited(text, start, length) - -- for IME input - input.candidate_text = {text = text, start= start, length = length} -end - function love.textinput(t) -- forward text input to SUIT suit.textinput(t) diff --git a/input.lua b/input.lua index cab9522..5262c2b 100644 --- a/input.lua +++ b/input.lua @@ -52,7 +52,7 @@ return function(core, input, ...) opt.state = core:registerHitbox(opt.id, x,y,w,h) opt.hasKeyboardFocus = core:grabKeyboardFocus(opt.id) - if (input.candidate_text.text == "") and opt.hasKeyboardFocus then + if opt.hasKeyboardFocus then local keycode,char = core:getPressedKey() -- text input if char and char ~= "" then diff --git a/theme.lua b/theme.lua index 53bdbf4..fe8b416 100644 --- a/theme.lua +++ b/theme.lua @@ -123,26 +123,12 @@ function theme.Input(input, opt, x,y,w,h) love.graphics.setFont(opt.font) love.graphics.print(input.text, x, y+(h-th)/2) - -- candidate text - local tw = opt.font:getWidth(input.text) - local ctw = opt.font:getWidth(input.candidate_text.text) - love.graphics.setColor((opt.color and opt.color.normal and opt.color.normal.fg) or theme.color.normal.fg) - love.graphics.print(input.candidate_text.text, x + tw, y+(h-th)/2) - - -- candidate text rectangle box - love.graphics.rectangle("line", x + tw, y+(h-th)/2, ctw, th) - -- cursor if opt.hasKeyboardFocus and (love.timer.getTime() % 1) > .5 then - local ct = input.candidate_text; - local ss = ct.text:sub(1, utf8.offset(ct.text, ct.start)) - local ws = opt.font:getWidth(ss) - if ct.start == 0 then ws = 0 end - love.graphics.setLineWidth(1) love.graphics.setLineStyle('rough') - love.graphics.line(x + opt.cursor_pos + ws, y + (h-th)/2, - x + opt.cursor_pos + ws, y + (h+th)/2) + love.graphics.line(x + opt.cursor_pos, y + (h-th)/2, + x + opt.cursor_pos, y + (h+th)/2) end -- reset scissor From 6960a80eaa012d67591987378b2c4369108dc0f3 Mon Sep 17 00:00:00 2001 From: endlesstravel Date: Sun, 6 Nov 2016 15:26:19 +0800 Subject: [PATCH 3/3] Revert "update to master" This reverts commit 10e79ca5c8a14651547a1340909e95f0cc1649a1. --- README.md | 17 ++++++++++++++--- input.lua | 2 +- theme.lua | 18 ++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b900540..7db2ffa 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,13 @@ More info and code is over at [readthedocs](http://suit.readthedocs.org/en/lates local suit = require 'suit' -- storage for text input -local input = {text = ""} +local input = {text = "", candidate_text = {text="", start=0, length=0}} + +-- make love use font which support CJK text +function love.load() + local font = love.graphics.newFont("NotoSansHans-Regular.otf", 20) + love.graphics.setFont(font) +end -- all the UI is defined in love.update or functions that are called from here function love.update(dt) @@ -33,7 +39,7 @@ function love.update(dt) -- put an input widget at the layout origin, with a cell size of 200 by 30 pixels suit.Input(input, suit.layout:row(200,30)) - + -- put a label that displays the text below the first cell -- the cell size is the same as the last one (200x30 px) -- the label text will be aligned to the left @@ -41,7 +47,7 @@ function love.update(dt) -- put an empty cell that has the same size as the last cell (200x30 px) suit.layout:row() - + -- put a button of size 200x30 px in the cell below -- if the button is pressed, quit the game if suit.Button("Close", suit.layout:row()).hit then @@ -54,6 +60,11 @@ function love.draw() suit.draw() end +function love.textedited(text, start, length) + -- for IME input + input.candidate_text = {text = text, start= start, length = length} +end + function love.textinput(t) -- forward text input to SUIT suit.textinput(t) diff --git a/input.lua b/input.lua index 5262c2b..cab9522 100644 --- a/input.lua +++ b/input.lua @@ -52,7 +52,7 @@ return function(core, input, ...) opt.state = core:registerHitbox(opt.id, x,y,w,h) opt.hasKeyboardFocus = core:grabKeyboardFocus(opt.id) - if opt.hasKeyboardFocus then + if (input.candidate_text.text == "") and opt.hasKeyboardFocus then local keycode,char = core:getPressedKey() -- text input if char and char ~= "" then diff --git a/theme.lua b/theme.lua index fe8b416..53bdbf4 100644 --- a/theme.lua +++ b/theme.lua @@ -123,12 +123,26 @@ function theme.Input(input, opt, x,y,w,h) love.graphics.setFont(opt.font) love.graphics.print(input.text, x, y+(h-th)/2) + -- candidate text + local tw = opt.font:getWidth(input.text) + local ctw = opt.font:getWidth(input.candidate_text.text) + love.graphics.setColor((opt.color and opt.color.normal and opt.color.normal.fg) or theme.color.normal.fg) + love.graphics.print(input.candidate_text.text, x + tw, y+(h-th)/2) + + -- candidate text rectangle box + love.graphics.rectangle("line", x + tw, y+(h-th)/2, ctw, th) + -- cursor if opt.hasKeyboardFocus and (love.timer.getTime() % 1) > .5 then + local ct = input.candidate_text; + local ss = ct.text:sub(1, utf8.offset(ct.text, ct.start)) + local ws = opt.font:getWidth(ss) + if ct.start == 0 then ws = 0 end + love.graphics.setLineWidth(1) love.graphics.setLineStyle('rough') - love.graphics.line(x + opt.cursor_pos, y + (h-th)/2, - x + opt.cursor_pos, y + (h+th)/2) + love.graphics.line(x + opt.cursor_pos + ws, y + (h-th)/2, + x + opt.cursor_pos + ws, y + (h+th)/2) end -- reset scissor