add on-screen keyboard widget

pull/190/head
chrox 11 years ago
parent 63ee951a19
commit fb4b6979f5

@ -0,0 +1,109 @@
require "ui/widget/container"
require "ui/widget/inputtext"
InputDialog = InputContainer:new{
title = "",
input = "",
input_hint = "",
buttons = nil,
input_type = nil,
width = nil,
height = nil,
title_face = Font:getFace("tfont", 22),
input_face = Font:getFace("cfont", 20),
title_padding = scaleByDPI(5),
title_margin = scaleByDPI(2),
input_padding = scaleByDPI(10),
input_margin = scaleByDPI(10),
button_padding = scaleByDPI(14),
}
function InputDialog:init()
self.title = FrameContainer:new{
padding = self.title_padding,
margin = self.title_margin,
bordersize = 0,
TextWidget:new{
text = self.title,
face = self.title_face,
width = self.width,
}
}
self.input = InputText:new{
text = self.input,
hint = self.input_hint,
face = self.input_face,
width = self.width * 0.9,
input_type = self.input_type,
scroll = false,
parent = self,
}
local button_table = ButtonTable:new{
width = self.width,
button_font_face = "cfont",
button_font_size = 20,
buttons = self.buttons,
zero_sep = true,
}
local title_bar = LineWidget:new{
--background = 8,
dimen = Geom:new{
w = button_table:getSize().w + self.button_padding,
h = scaleByDPI(2),
}
}
self.dialog_frame = FrameContainer:new{
radius = 8,
bordersize = 3,
padding = 0,
margin = 0,
background = 0,
VerticalGroup:new{
align = "left",
self.title,
title_bar,
-- input
CenterContainer:new{
dimen = Geom:new{
w = title_bar:getSize().w,
h = self.input:getSize().h,
},
self.input,
},
-- buttons
CenterContainer:new{
dimen = Geom:new{
w = title_bar:getSize().w,
h = button_table:getSize().h,
},
button_table,
}
}
}
self[1] = CenterContainer:new{
dimen = Geom:new{
w = Screen:getWidth(),
h = Screen:getHeight() - self.input:getKeyboardDimen().h,
},
self.dialog_frame,
}
UIManager.repaint_all = true
UIManager.full_refresh = true
end
function InputDialog:onShowKeyboard()
self.input:onShowKeyboard()
end
function InputDialog:getInputText()
return self.input:getText()
end
function InputDialog:onClose()
self.input:onCloseKeyboard()
end

@ -0,0 +1,137 @@
require "ui/graphics"
require "ui/widget/text"
require "ui/widget/keyboard"
require "ui/widget/container"
InputText = InputContainer:new{
text = "",
hint = "demo hint",
charlist = {}, -- table to store input string
charpos = 1,
input_type = nil,
width = nil,
height = nil,
face = Font:getFace("cfont", 22),
padding = 5,
margin = 5,
bordersize = 2,
parent = nil, -- parent dialog that will be set dirty
scroll = false,
}
function InputText:init()
self:StringToCharlist(self.text)
self:initTextBox()
self:initKeyboard()
end
function InputText:initTextBox()
local bgcolor = nil
local fgcolor = nil
if self.text == "" then
self.text = self.hint
bgcolor = 0.0
fgcolor = 0.5
else
bgcolor = 0.0
fgcolor = 1.0
end
local text_widget = nil
if self.scroll then
text_widget = ScrollTextWidget:new{
text = self.text,
face = self.face,
bgcolor = bgcolor,
fgcolor = fgcolor,
width = self.width,
height = self.height,
}
else
text_widget = TextBoxWidget:new{
text = self.text,
face = self.face,
bgcolor = bgcolor,
fgcolor = fgcolor,
width = self.width,
height = self.height,
}
end
self[1] = FrameContainer:new{
bordersize = self.bordersize,
padding = self.padding,
margin = self.margin,
text_widget,
}
self.dimen = self[1]:getSize()
end
function InputText:initKeyboard()
local keyboard_layout = 2
if self.input_type == "number" then
keyboard_layout = 3
end
self.keyboard = VirtualKeyboard:new{
layout = keyboard_layout,
inputbox = self,
}
end
function InputText:onShowKeyboard()
UIManager:show(self.keyboard)
end
function InputText:onCloseKeyboard()
UIManager:close(self.keyboard)
end
function InputText:getKeyboardDimen()
return self.keyboard.dimen
end
function InputText:addChar(char)
table.insert(self.charlist, self.charpos, char)
self.charpos = self.charpos + 1
self.text = self:CharlistToString()
self:initTextBox()
UIManager:setDirty(self.parent, "partial")
end
function InputText:delChar()
self.charpos = self.charpos - 1
table.remove(self.charlist, self.charpos)
self.text = self:CharlistToString()
self:initTextBox()
UIManager:setDirty(self.parent, "partial")
end
function InputText:getText()
return self.text
end
function InputText:StringToCharlist(text)
if text == nil then return end
-- clear
self.charlist = {}
self.charpos = 1
local prevcharcode, charcode = 0
for uchar in string.gfind(text, "([%z\1-\127\194-\244][\128-\191]*)") do
charcode = util.utf8charcode(uchar)
if prevcharcode then -- utf8
self.charlist[#self.charlist+1] = uchar
end
prevcharcode = charcode
end
self.text = self:CharlistToString()
self.charpos = #self.charlist+1
end
function InputText:CharlistToString()
local s, i = ""
for i=1, #self.charlist do
s = s .. self.charlist[i]
end
return s
end

@ -0,0 +1,298 @@
require "ui/font"
require "ui/widget/text"
require "ui/widget/image"
require "ui/widget/group"
require "ui/widget/container"
VirtualKey = InputContainer:new{
key = nil,
icon = nil,
label = nil,
keyboard = nil,
callback = nil,
width = nil,
height = nil,
bordersize = 2,
face = Font:getFace("infont", 22),
}
function VirtualKey:init()
if self.label == "Sym" or self.label == "ABC" then
self.callback = function () self.keyboard:setLayout(self.key or self.label) end
elseif self.label == "Shift" then
self.callback = function () self.keyboard:setLayout(self.key or self.label) end
elseif self.label == "IM" then
self.callback = function () self.keyboard:setLayout(self.key or self.label) end
elseif self.label == "Backspace" then
self.callback = function () self.keyboard:delChar() end
else
self.callback = function () self.keyboard:addChar(self.key) end
end
local label_widget = nil
if self.icon then
label_widget = ImageWidget:new{
file = self.icon,
}
else
label_widget = TextWidget:new{
text = self.label,
face = self.face,
}
end
self[1] = FrameContainer:new{
margin = 0,
bordersize = self.bordersize,
background = 0,
radius = 5,
padding = 0,
CenterContainer:new{
dimen = Geom:new{
w = self.width - 2*self.bordersize,
h = self.height - 2*self.bordersize,
},
label_widget,
},
}
self.dimen = Geom:new{
w = self.width,
h = self.height,
}
--self.dimen = self[1]:getSize()
if Device:isTouchDevice() then
self.ges_events = {
TapSelect = {
GestureRange:new{
ges = "tap",
range = self.dimen,
},
},
DoubleTapSelect = {
GestureRange:new{
ges = "double_tap",
range = self.dimen,
},
},
}
end
end
function VirtualKey:onTapSelect()
self[1].invert = true
if self.callback then
self.callback()
end
UIManager:scheduleIn(0.08, function() self:invert(false) end)
return true
end
function VirtualKey:onDoubleTapSelect()
self[1].invert = true
if self.callback then
self.callback() -- once
self.callback() -- twice
end
UIManager:scheduleIn(0.08, function() self:invert(false) end)
return true
end
function VirtualKey:invert(invert)
self[1].invert = invert
UIManager.update_region_func = function()
DEBUG("update key region", self[1].dimen)
return self[1].dimen
end
UIManager:setDirty(self.keyboard, "partial")
end
VirtualKeyboard = InputContainer:new{
is_always_active = true,
inputbox = nil,
KEYS = {}, -- table to store layouts
min_layout = 2,
max_layout = 9,
layout = 2,
shiftmode = false,
symbolmode = false,
utf8mode = false,
width = 600,
height = 256,
bordersize = 2,
padding = 2,
key_padding = scaleByDPI(6),
}
function VirtualKeyboard:init()
self.KEYS = {
-- first row
{
{ "Q", "q", "1", "!", "Я", "я", "1", "!", },
{ "W", "w", "2", "?", "Ж", "ж", "2", "?", },
{ "E", "e", "3", "|", "Е", "е", "3", "«", },
{ "R", "r", "4", "#", "Р", "р", "4", "»", },
{ "T", "t", "5", "@", "Т", "т", "5", ":", },
{ "Y", "y", "6", "", "Ы", "ы", "6", ";", },
{ "U", "u", "7", "'", "У", "у", "7", "~", },
{ "I", "i", "8", "`", "И", "и", "8", "(", },
{ "O", "o", "9", ":", "О", "о", "9", ")", },
{ "P", "p", "0", ";", "П", "п", "0", "=", },
},
-- second raw
{
{ "A", "a", "+", "", "А", "а", "Ш", "ш", },
{ "S", "s", "-", "_", "С", "с", "Ѕ", "ѕ", },
{ "D", "d", "*", "=", "Д", "д", "Э", "э", },
{ "F", "f", "/", "\\", "Ф", "ф", "Ю", "ю", },
{ "G", "g", "%", "", "Г", "г", "Ґ", "ґ", },
{ "H", "h", "^", "", "Ч", "ч", "Ј", "ј", },
{ "J", "j", "<", "", "Й", "й", "І", "і", },
{ "K", "k", "=", "\"", "К", "к", "Ќ", "ќ", },
{ "L", "l", ">", "~", "Л", "л", "Љ", "љ", },
},
-- third raw
{
{ label = "Shift",
icon = "resources/icons/appbar.arrow.shift.png",
width = 1.5
},
{ "Z", "z", "(", "$", "З", "з", "Щ", "щ", },
{ "X", "x", ")", "", "Х", "х", "", "@", },
{ "C", "c", "&", "¥", "Ц", "ц", "Џ", "џ", },
{ "V", "v", ":", "£", "В", "в", "Ў", "ў", },
{ "B", "b", "π", "", "Б", "б", "Ћ", "ћ", },
{ "N", "n", "е", "", "Н", "н", "Њ", "њ", },
{ "M", "m", "~", "", "М", "м", "Ї", "ї", },
{ label = "Backspace",
icon = "resources/icons/appbar.clear.reflect.horizontal.png",
width = 1.5
},
},
-- fourth raw
{
{ "Sym", "Sym", "ABC", "ABC", "Sym", "Sym", "ABC", "ABC",
width = 1.5},
{ label = "IM",
icon = "resources/icons/appbar.globe.wire.png",
},
{ label = "space",
" ", " ", " ", " ", " ", " ", " ", " ",
width = 5.0},
{ ",", ".", ".", ",", ",", ".", "Є", "є", },
{ label = "Enter",
"\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n",
icon = "resources/icons/appbar.arrow.enter.png",
width = 1.5,
},
}
}
self:initLayout(self.layout)
end
function VirtualKeyboard:initLayout(layout)
local function VKLayout(b1, b2, b3)
local function boolnum(bool)
return bool and 1 or 0
end
return 2 - boolnum(b1) + 2 * boolnum(b2) + 4 * boolnum(b3)
end
if layout then
-- to be sure layout is selected properly
layout = math.max(layout, self.min_layout)
layout = math.min(layout, self.max_layout)
self.layout = layout
-- fill the layout modes
layout = layout % 4
self.shiftmode = (layout == 1 or layout == 3)
self.symbolmode = (layout == 3 or layout == 4)
self.utf8mode = (self.layout > 5)
else -- or, without input parameter, restore layout from current layout modes
self.layout = VKLayout(self.shiftmode, self.symbolmode, self.utf8mode)
end
self:addKeys()
end
function VirtualKeyboard:addKeys()
local base_key_width = math.floor((self.width - 11*self.key_padding - 2*self.padding)/10)
local base_key_height = math.floor((self.height - 5*self.key_padding - 2*self.padding)/4)
local h_key_padding = HorizontalSpan:new{width = self.key_padding}
local v_key_padding = VerticalSpan:new{width = self.key_padding}
local vertical_group = VerticalGroup:new{}
for i = 1, #self.KEYS do
local horizontal_group = HorizontalGroup:new{}
for j = 1, #self.KEYS[i] do
local width_factor = self.KEYS[i][j].width or 1.0
local key_width = math.floor((base_key_width + self.key_padding) * width_factor)
- self.key_padding
local key_height = base_key_height
local label = self.KEYS[i][j].label or self.KEYS[i][j][self.layout]
local key = VirtualKey:new{
key = self.KEYS[i][j][self.layout],
icon = self.KEYS[i][j].icon,
label = label,
keyboard = self,
width = key_width,
height = key_height,
}
table.insert(horizontal_group, key)
if j ~= #self.KEYS[i] then
table.insert(horizontal_group, h_key_padding)
end
end
table.insert(vertical_group, horizontal_group)
if i ~= #self.KEYS then
table.insert(vertical_group, v_key_padding)
end
end
local size = vertical_group:getSize()
local keyboard_frame = FrameContainer:new{
margin = 0,
bordersize = self.bordersize,
background = 0,
radius = 0,
padding = self.padding,
CenterContainer:new{
dimen = Geom:new{
w = self.width - 2*self.bordersize -2*self.padding,
h = self.height - 2*self.bordersize -2*self.padding,
},
vertical_group,
}
}
self[1] = BottomContainer:new{
dimen = Screen:getSize(),
keyboard_frame,
}
self.dimen = keyboard_frame:getSize()
end
function VirtualKeyboard:setLayout(key)
if key == "Shift" then
self.shiftmode = not self.shiftmode
elseif key == "Sym" or key == "ABC" then
self.symbolmode = not self.symbolmode
elseif key == "IM" then
self.utf8mode = not self.utf8mode
end
self:initLayout()
UIManager:setDirty(self, "partial")
end
function VirtualKeyboard:addChar(key)
DEBUG("add char", key)
self.inputbox:addChar(key)
UIManager:setDirty(self, "partial")
UIManager:setDirty(self.inputbox, "partial")
end
function VirtualKeyboard:delChar()
DEBUG("delete char")
self.inputbox:delChar()
UIManager:setDirty(self, "partial")
UIManager:setDirty(self.inputbox, "partial")
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="48"
height="48"
viewBox="0 0 48 48"
enable-background="new 0 0 76.00 76.00"
xml:space="preserve"
id="svg2"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="appbar.arrow.left.svg"><metadata
id="metadata10"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs8" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="1022"
id="namedview6"
showgrid="true"
inkscape:zoom="6.2105263"
inkscape:cx="84.532094"
inkscape:cy="4.7422124"
inkscape:window-x="1280"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"><inkscape:grid
type="xygrid"
id="grid2989"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview>
<path
d="M 43,30.428571 43,23.571429 43,9 l -6.918919,0 0,14.571429 -20.756756,0 0,-8.571429 L 3,27 l 12.324325,12 0,-8.571429 24.216216,0"
id="path4"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-linejoin:round"
sodipodi:nodetypes="ccccccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 57,42L 57,34L 32.25,34L 42.25,24L 31.75,24L 17.75,38L 31.75,52L 42.25,52L 32.25,42L 57,42 Z "/>
</svg>

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="48"
height="48"
viewBox="0 0 48 48"
enable-background="new 0 0 76.00 76.00"
xml:space="preserve"
id="svg2"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="appbar.arrow.shift.svg"
inkscape:export-filename="/home/chrox/dev/koreader/resources/icons/appbar.arrow.shift.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><metadata
id="metadata10"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs8" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="1022"
id="namedview6"
showgrid="true"
inkscape:zoom="12.421053"
inkscape:cx="17.939741"
inkscape:cy="18.973393"
inkscape:window-x="1280"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"><inkscape:grid
type="xygrid"
id="grid2987"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview>
<path
d="m 17,35 14,0 -0.003,-11.537082 8.076834,0 L 24.074,10 9.0741666,23.462918 l 8.0768334,0 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:3;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:nodetypes="cccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="appbar_globe" Width="48" Height="48" Clip="F1 M 0,0L 48,0L 48,48L 0,48L 0,0" UseLayoutRounding="False">
<Path Width="28" Height="28" Canvas.Left="10" Canvas.Top="10" Stretch="Fill" Fill="#FF000000" Data="F1 M 24,10C 31.732,10 38,16.268 38,24C 38,31.732 31.732,38 24,38C 16.268,38 10,31.732 10,24C 10,16.268 16.268,10 24,10 Z M 12.0411,23L 16.0201,23C 16.0868,21.3448 16.318,19.7691 16.6852,18.3225C 15.7561,17.9966 14.8987,17.6104 14.1296,17.1735C 12.9651,18.854 12.2187,20.8464 12.0411,23 Z M 18.9815,13.0966C 17.6191,13.7247 16.3945,14.6007 15.3654,15.6667C 15.9385,15.9635 16.5723,16.2309 17.2568,16.4638C 17.7247,15.1844 18.3082,14.0469 18.9815,13.0966 Z M 23,23L 23,19.4799C 21.4398,19.417 19.9502,19.208 18.5728,18.8767C 18.2729,20.1449 18.0809,21.5352 18.0205,23L 23,23 Z M 23,12.1659C 21.4121,12.6987 20.0356,14.479 19.1312,16.9856C 20.3301,17.252 21.6325,17.4231 23,17.4795L 23,12.1659 Z M 35.9589,23C 35.7812,20.8464 35.0349,18.8541 33.8703,17.1735C 33.1013,17.6104 32.2439,17.9966 31.3148,18.3225C 31.682,19.7691 31.9131,21.3448 31.9799,23L 35.9589,23 Z M 29.0185,13.0966C 29.6918,14.0469 30.2753,15.1844 30.7432,16.4638C 31.4277,16.2309 32.0614,15.9635 32.6346,15.6667C 31.6055,14.6007 30.3809,13.7247 29.0185,13.0966 Z M 25,23L 29.9794,23C 29.919,21.5352 29.727,20.145 29.4272,18.8767C 28.0498,19.208 26.5602,19.417 25,19.4799L 25,23 Z M 25,12.1659L 25,17.4795C 26.3674,17.4231 27.6699,17.252 28.8687,16.9856C 27.9643,14.479 26.5879,12.6987 25,12.1659 Z M 35.9589,25L 31.9799,25C 31.9131,26.6552 31.682,28.2309 31.3148,29.6775C 32.2439,30.0035 33.1013,30.3896 33.8704,30.8266C 35.0349,29.146 35.7812,27.1536 35.9589,25 Z M 29.0184,34.9034C 30.3809,34.2753 31.6055,33.3993 32.6346,32.3333C 32.0614,32.0365 31.4277,31.7691 30.7432,31.5362C 30.2753,32.8156 29.6918,33.9531 29.0184,34.9034 Z M 25,25L 25,28.5201C 26.5602,28.583 28.0498,28.792 29.4272,29.1233C 29.7271,27.8551 29.919,26.4648 29.9794,25L 25,25 Z M 25,35.8341C 26.5879,35.3013 27.9643,33.521 28.8687,31.0144C 27.6699,30.748 26.3674,30.5769 25,30.5205L 25,35.8341 Z M 12.0411,25C 12.2187,27.1536 12.9651,29.146 14.1296,30.8266C 14.8987,30.3897 15.7561,30.0035 16.6852,29.6775C 16.318,28.2309 16.0868,26.6552 16.0201,25L 12.0411,25 Z M 18.9815,34.9034C 18.3082,33.9531 17.7247,32.8156 17.2568,31.5362C 16.5723,31.7691 15.9385,32.0365 15.3654,32.3333C 16.3945,33.3994 17.6191,34.2753 18.9815,34.9034 Z M 23,25L 18.0205,25C 18.0809,26.4648 18.2729,27.8551 18.5728,29.1233C 19.9502,28.792 21.4398,28.583 23,28.5201L 23,25 Z M 23,35.8341L 23,30.5205C 21.6325,30.5769 20.3301,30.748 19.1312,31.0144C 20.0356,33.521 21.4121,35.3013 23,35.8341 Z "/>
</Canvas>
Loading…
Cancel
Save