Skip to content

Commit 3d225ef

Browse files
committed
Add a canvas widget + example
1 parent ad8d5ba commit 3d225ef

File tree

5 files changed

+144
-5
lines changed

5 files changed

+144
-5
lines changed

examples/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# LTK - Copyright 2023 - All Rights Reserved - chrislaffra.com - See LICENSE
22

33
from examples import app
4+
from examples import canvas
45
from examples import custom
56
from examples import documentation
67
from examples import dom
@@ -28,7 +29,7 @@
2829
("examples/documentation.py", documentation.create()),
2930
("examples/pitch.py", pitch.create()),
3031
("examples/svg.py", svg.create()),
31-
("examples/pydata.py", pydata.create()),
32+
("examples/canvas.py", canvas.create()),
3233
("examples/pizza.py", pizza.create()),
3334
("examples/splits.py", splits.create()),
3435
("examples/tutorial.py", tutorial.create()),

examples/canvas.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# LTK - Copyright 2023 - All Rights Reserved - chrislaffra.com - See LICENSE
2+
3+
import ltk
4+
import random
5+
6+
7+
def create():
8+
9+
canvas = ltk.Canvas()
10+
colors = ["black", "red", "white", "blue", "green", "yellow", "purple", "teal", "orange"]
11+
12+
def mousemove(event):
13+
canvas.fillStyle = random.choice(colors)
14+
canvas.fillRect(event.offsetX, event.offsetY, 50, 50)
15+
16+
return (
17+
ltk.VBox(
18+
ltk.Heading2("This is a Canvas. Move the mouse draw squares."),
19+
canvas
20+
.attr("id", "pink-canvas") \
21+
.on("mousemove", ltk.proxy(mousemove)) \
22+
.attr("width", "500px") \
23+
.attr("height", "500px") \
24+
.css("width", "500px") \
25+
.css("height", "500px") \
26+
.css("border", "1px solid gray") \
27+
.css("background", "pink")
28+
)
29+
.attr("name", "Canvas")
30+
)

kitchensink.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ files = [
99
"ltk/ltk.css",
1010
"examples/__init__.py",
1111
"examples/app.py",
12+
"examples/canvas.py",
1213
"examples/documentation.py",
1314
"examples/dom.py",
1415
"examples/inputs.py",

ltk/ltk.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,66 @@
8686
window.ltk_delete = (url, success, error) => {
8787
$.ajax({ url, type: "DELETE", success}).fail(error)
8888
}
89+
90+
window.canvasRects = (context, coordinatesJson) => {
91+
const coordinates = JSON.parse(coordinatesJson)
92+
context.beginPath()
93+
for (var n = 0; n < coordinates.length; n += 5) {
94+
x = coordinates[n]
95+
y = coordinates[n + 1]
96+
w = coordinates[n + 2]
97+
h = coordinates[n + 3]
98+
context.rect(x, y, w, h)
99+
}
100+
}
101+
102+
window.canvasFillRects = (context, coordinatesJson) => {
103+
const coordinates = JSON.parse(coordinatesJson)
104+
context.beginPath()
105+
for (var n = 0; n < coordinates.length; n += 5) {
106+
x = coordinates[n]
107+
y = coordinates[n + 1]
108+
w = coordinates[n + 2]
109+
h = coordinates[n + 3]
110+
context.fillStyle = coordinates[n + 4]
111+
context.fillRect(x, y, w, h)
112+
}
113+
}
114+
115+
window.canvasDrawTexts = (context, coordinatesJson) => {
116+
const coordinates = JSON.parse(coordinatesJson)
117+
context.beginPath()
118+
context.strokeStyle = "red"
119+
for (var n = 0; n < coordinates.length; n += 5) {
120+
x = coordinates[n]
121+
y = coordinates[n + 1]
122+
text = coordinates[n + 2]
123+
color = coordinates[n + 3]
124+
w = coordinates[n + 4]
125+
context.fillStyle = color
126+
context.fillText(text, x, y, w)
127+
}
128+
}
129+
130+
window.canvasDrawLines = (context, lineWidth, strokeStyle, coordinatesJson) => {
131+
const coordinates = JSON.parse(coordinatesJson)
132+
context.lineWidth = lineWidth
133+
context.strokeStyle = strokeStyle
134+
for (var n = 0; n < coordinates.length; n += 4) {
135+
context.beginPath()
136+
context.moveTo(coordinates[n], coordinates[n + 1])
137+
context.lineTo(coordinates[n + 2], coordinates[n + 3])
138+
context.stroke()
139+
}
140+
}
141+
142+
$.fn.isInViewport = function() {
143+
const offset = $(this).offset();
144+
if (!offset) return true
145+
const elementTop = $(this).offset().top;
146+
const elementBottom = elementTop + $(this).outerHeight();
147+
const viewportTop = $(window).scrollTop() + 50;
148+
const viewportBottom = viewportTop + $(window).height();
149+
return elementBottom > viewportTop && elementTop < viewportBottom;
150+
};
89151
})()

ltk/widgets.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from pyscript import window # type: ignore
44
from ltk.jquery import *
5+
import itertools
6+
import json
57
import logging
8+
import math
69

710
__all__ = [
811
"HBox", "Div", "VBox", "Container", "Card", "Preformatted", "Text", "Input", "Checkbox",
@@ -84,8 +87,6 @@ def _flatten(self, children):
8487
result.append(child)
8588
return result
8689

87-
names = set()
88-
8990
def css(self, property, value=None):
9091
"""
9192
Get or set a computed style property.
@@ -329,8 +330,6 @@ def animate(self, properties, duration=None, easing=None, complete=None):
329330
return self.element.animate(properties, duration, easing, proxy(complete))
330331

331332
def __getattr__(self, name):
332-
if not name in self.names:
333-
self.names.add(name)
334333
try:
335334
return getattr(self.element, name)
336335
except:
@@ -1130,6 +1129,52 @@ def show(self):
11301129
widget.on(event, ltk.proxy(lambda *args: self.event(index)))
11311130

11321131

1132+
class Canvas(Widget):
1133+
classes = [ "ltk-canvas" ]
1134+
tag = "canvas"
1135+
1136+
def __init__(self, style=DEFAULT_CSS) -> None:
1137+
self._context = None
1138+
Widget.__init__(self, style)
1139+
1140+
def __getattr__(self, name):
1141+
try:
1142+
return getattr(self.element, name)
1143+
except:
1144+
try:
1145+
return getattr(self.context, name)
1146+
except:
1147+
raise AttributeError(f"LTK widget {self} does not have attribute {name}")
1148+
1149+
def __setattr__(self, name, value):
1150+
if name != "_context" and self._context and hasattr(self._context, name):
1151+
setattr(self._context, name, value)
1152+
elif name != "_context" and hasattr(self.element, name):
1153+
setattr(self.element, name, value)
1154+
else:
1155+
super().__setattr__(name, value)
1156+
1157+
@property
1158+
def context(self):
1159+
if self._context == None:
1160+
window.console.log("Create context", self.element, self.element[0])
1161+
self._context = self.element[0].getContext("2d")
1162+
return self._context
1163+
1164+
def rects(self, rects):
1165+
return window.canvasRects(self.context, json.dumps(list(rects)))
1166+
1167+
def fill_rects(self, rects):
1168+
return window.canvasFillRects(self.context, json.dumps(list(rects)))
1169+
1170+
def lines(self, lines, width, color):
1171+
return window.canvasDrawLines(self.context, width, color, json.dumps(list(lines)))
1172+
1173+
def texts(self, texts, font):
1174+
self.set_font(font)
1175+
return window.canvasDrawTexts(self.context, json.dumps(list(texts)))
1176+
1177+
11331178
def _close_all_menus(event=None):
11341179
if event and jQuery(event.target).hasClass("ltk-menulabel"):
11351180
return

0 commit comments

Comments
 (0)