hokusai-zero 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +4 -0
- data/README.md +2 -0
- data/ast/src/core/hml.c +9 -9
- data/ast/src/core/text.c +1 -3
- data/ast/test/text.c +3 -3
- data/docs.sh +29 -0
- data/ext/extconf.rb +50 -14
- data/grammar/corpus/1_document.txt +24 -0
- data/grammar/corpus/6_styles.txt +23 -0
- data/grammar/grammar.js +4 -4
- data/grammar/src/grammar.json +19 -19
- data/grammar/src/parser.c +1904 -1956
- data/grammar/test.nml +10 -8
- data/hokusai.gemspec +2 -1
- data/ui/examples/assets/Delius-Regular.ttf +0 -0
- data/ui/examples/assets/DoHyeon.ttf +0 -0
- data/ui/examples/assets/Inter-Regular.ttf +0 -0
- data/ui/examples/assets/ernest.gif +0 -0
- data/ui/examples/assets/icons/audio-x-generic.png +0 -0
- data/ui/examples/assets/icons/image-x-generic.png +0 -0
- data/ui/examples/assets/icons/media-playback-pause.png +0 -0
- data/ui/examples/assets/icons/media-playback-start.png +0 -0
- data/ui/examples/assets/icons/media-playback-stop.png +0 -0
- data/ui/examples/assets/icons/package-x-generic.png +0 -0
- data/ui/examples/assets/icons/text-x-generic.png +0 -0
- data/ui/examples/assets/icons/video-x-generic.png +0 -0
- data/ui/examples/buddy.rb +16 -14
- data/ui/examples/clock.rb +38 -36
- data/ui/examples/counter.rb +100 -98
- data/ui/examples/dynamic.rb +115 -113
- data/ui/examples/foobar.rb +189 -187
- data/ui/examples/forum/file.rb +54 -0
- data/ui/examples/forum/music.rb +76 -0
- data/ui/examples/forum/post.rb +146 -0
- data/ui/examples/forum.rb +198 -0
- data/ui/examples/spreadsheet/csv.rb +261 -0
- data/ui/examples/spreadsheet.rb +138 -0
- data/ui/examples/stock.rb +86 -92
- data/ui/examples/stock_decider/option.rb +1 -1
- data/ui/examples/tic_tac_toe.rb +193 -191
- data/ui/lib/lib_hokusai.rb +0 -1
- data/ui/src/hokusai/ast.rb +42 -43
- data/ui/src/hokusai/backends/raylib/font.rb +1 -2
- data/ui/src/hokusai/backends/raylib.rb +16 -7
- data/ui/src/hokusai/backends/sdl2/font.rb +13 -9
- data/ui/src/hokusai/backends/sdl2.rb +5 -5
- data/ui/src/hokusai/block.rb +7 -0
- data/ui/src/hokusai/blocks/hblock.rb +2 -2
- data/ui/src/hokusai/blocks/image.rb +5 -1
- data/ui/src/hokusai/blocks/input.rb +17 -0
- data/ui/src/hokusai/blocks/label.rb +5 -2
- data/ui/src/hokusai/blocks/text.rb +10 -5
- data/ui/src/hokusai/blocks/titlebar/osx.rb +4 -4
- data/ui/src/hokusai/blocks/variable.rb +33 -0
- data/ui/src/hokusai/blocks/vblock.rb +1 -1
- data/ui/src/hokusai/commands/rect.rb +4 -4
- data/ui/src/hokusai/commands.rb +33 -31
- data/ui/src/hokusai/diff.rb +11 -0
- data/ui/src/hokusai/event.rb +19 -5
- data/ui/src/hokusai/events/mouse.rb +9 -1
- data/ui/src/hokusai/font.rb +60 -0
- data/ui/src/hokusai/meta.rb +10 -16
- data/ui/src/hokusai/node.rb +1 -1
- data/ui/src/hokusai/painter.rb +1 -2
- data/ui/src/hokusai/util/clamping_iterator.rb +5 -6
- data/ui/src/hokusai.rb +36 -4
- metadata +37 -3
data/ui/examples/foobar.rb
CHANGED
@@ -12,216 +12,218 @@ require_relative "./tic_tac_toe"
|
|
12
12
|
require "json"
|
13
13
|
require "net/http"
|
14
14
|
|
15
|
-
module
|
16
|
-
|
17
|
-
|
18
|
-
# Displays post content, and throws in a game of Tic Tac Toe for good measure.
|
19
|
-
#
|
20
|
-
# Takes up full width on small viewport, centers on large viewport
|
21
|
-
# Duplication in the template can easily be extracted into a separate block
|
22
|
-
class Post < Hokusai::Block
|
23
|
-
style <<~EOF
|
24
|
-
[style]
|
25
|
-
textStyle {
|
26
|
-
color: rgb(243, 243, 243);
|
27
|
-
}
|
28
|
-
EOF
|
29
|
-
|
30
|
-
template <<~EOF
|
31
|
-
[template]
|
32
|
-
hblock
|
33
|
-
vblock.about { width="80" :background="about_background" }
|
34
|
-
image {
|
35
|
-
width="80"
|
36
|
-
height="80"
|
37
|
-
:source="about_image_source"
|
38
|
-
}
|
39
|
-
label { ...textStyle :content="post_author_name" size="9" }
|
40
|
-
vblock.content
|
41
|
-
text { ...textStyle markdown="true" :content="post_title" size="20" }
|
42
|
-
text {
|
43
|
-
:content="post_body"
|
44
|
-
:padding="text_padding"
|
45
|
-
size="17
|
46
|
-
...textStyle
|
47
|
-
@height_updated="text_height_updated"
|
48
|
-
}
|
49
|
-
tic_tac_toe { height="400" width="400" }
|
50
|
-
EOF
|
51
|
-
|
52
|
-
# Mandatory props
|
53
|
-
computed! :entry
|
54
|
-
computed! :index
|
55
|
-
|
56
|
-
# injects the screen type from the provisioned value
|
57
|
-
# and aliases it to :media_type
|
58
|
-
inject :screen_type, :media_type
|
59
|
-
|
60
|
-
uses(
|
61
|
-
hblock: Hokusai::Blocks::Hblock,
|
62
|
-
vblock: Hokusai::Blocks::Vblock,
|
63
|
-
image: Hokusai::Blocks::Image,
|
64
|
-
text: Hokusai::Blocks::Text,
|
65
|
-
label: Hokusai::Blocks::Label,
|
66
|
-
empty: Hokusai::Blocks::Empty,
|
67
|
-
tic_tac_toe: TicTacToe::App
|
68
|
-
)
|
69
|
-
#text { @height_updated="save_height" :padding="text_padding" :content="content" :color="label_color" }
|
70
|
-
|
71
|
-
# This block is rendered inside a scrollable panel
|
72
|
-
# in between a `clip begin` and `clip end` command
|
15
|
+
module Demos
|
16
|
+
module Demo
|
17
|
+
# Block that represents a singular post
|
73
18
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
19
|
+
# Displays post content, and throws in a game of Tic Tac Toe for good measure.
|
20
|
+
#
|
21
|
+
# Takes up full width on small viewport, centers on large viewport
|
22
|
+
# Duplication in the template can easily be extracted into a separate block
|
23
|
+
class Post < Hokusai::Block
|
24
|
+
style <<~EOF
|
25
|
+
[style]
|
26
|
+
textStyle {
|
27
|
+
color: rgb(243, 243, 243);
|
28
|
+
}
|
29
|
+
EOF
|
30
|
+
|
31
|
+
template <<~EOF
|
32
|
+
[template]
|
33
|
+
hblock
|
34
|
+
vblock.about { width="80" :background="about_background" }
|
35
|
+
image {
|
36
|
+
width="80"
|
37
|
+
height="80"
|
38
|
+
:source="about_image_source"
|
39
|
+
}
|
40
|
+
label { ...textStyle :content="post_author_name" size="9" }
|
41
|
+
vblock.content
|
42
|
+
text { ...textStyle markdown="true" :content="post_title" size="20" }
|
43
|
+
text {
|
44
|
+
:content="post_body"
|
45
|
+
:padding="text_padding"
|
46
|
+
size="17
|
47
|
+
...textStyle
|
48
|
+
@height_updated="text_height_updated"
|
49
|
+
}
|
50
|
+
tic_tac_toe { height="400" width="400" }
|
51
|
+
EOF
|
52
|
+
|
53
|
+
# Mandatory props
|
54
|
+
computed! :entry
|
55
|
+
computed! :index
|
56
|
+
|
57
|
+
# injects the screen type from the provisioned value
|
58
|
+
# and aliases it to :media_type
|
59
|
+
inject :screen_type, :media_type
|
60
|
+
|
61
|
+
uses(
|
62
|
+
hblock: Hokusai::Blocks::Hblock,
|
63
|
+
vblock: Hokusai::Blocks::Vblock,
|
64
|
+
image: Hokusai::Blocks::Image,
|
65
|
+
text: Hokusai::Blocks::Text,
|
66
|
+
label: Hokusai::Blocks::Label,
|
67
|
+
empty: Hokusai::Blocks::Empty,
|
68
|
+
tic_tac_toe: TicTacToe::App
|
69
|
+
)
|
70
|
+
#text { @height_updated="save_height" :padding="text_padding" :content="content" :color="label_color" }
|
71
|
+
|
72
|
+
# This block is rendered inside a scrollable panel
|
73
|
+
# in between a `clip begin` and `clip end` command
|
74
|
+
#
|
75
|
+
# `Hokusai.can_render(canvas) will tell us if this block will even be visible`
|
76
|
+
# If not visible, I won't waste memory / cpu to render
|
77
|
+
def render(canvas)
|
78
|
+
if Hokusai.can_render(canvas)
|
79
|
+
yield(canvas)
|
80
|
+
end
|
79
81
|
end
|
80
|
-
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
def post_index
|
87
|
-
index.to_s
|
88
|
-
end
|
83
|
+
def small_viewport
|
84
|
+
media_type == :small
|
85
|
+
end
|
89
86
|
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
def post_index
|
88
|
+
index.to_s
|
89
|
+
end
|
93
90
|
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
def post_body
|
92
|
+
entry.content
|
93
|
+
end
|
97
94
|
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
def post_title
|
96
|
+
@post_title ||= "_#{entry.title.upcase}_ - #{DateTime.now.strftime("%m/%d/%Y %H:%M %p")}"
|
97
|
+
end
|
101
98
|
|
102
|
-
|
103
|
-
|
104
|
-
|
99
|
+
def post_author_name
|
100
|
+
@post_author_name ||= entry.id.even? ? "Adeline" : "Skinnyjames"
|
101
|
+
end
|
105
102
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
def about_background
|
110
|
-
@author_background ||= Hokusai::Color.new(155,155,155,20)
|
111
|
-
end
|
103
|
+
def about_image_source
|
104
|
+
@about_image_source ||= entry.id.even? ? "#{__dir__}/assets/addy.png" : "#{__dir__}/assets/baby_sean.png"
|
105
|
+
end
|
112
106
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
107
|
+
# Cache the color so that it isn't recomputing at `O(n) complexity`
|
108
|
+
#
|
109
|
+
# TODO: extract styling / prop declaration into separate template
|
110
|
+
def about_background
|
111
|
+
@author_background ||= Hokusai::Color.new(155,155,155,20)
|
112
|
+
end
|
117
113
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
def text_height_updated(height)
|
123
|
-
# tic tac toe height + about node height || content height + padding
|
124
|
-
@height = 400 + [80, height].max + 80
|
125
|
-
end
|
114
|
+
# Cache the text padding
|
115
|
+
def text_padding
|
116
|
+
@text_padding ||= Hokusai::Padding.new(20, 5, 20, 5)
|
117
|
+
end
|
126
118
|
|
127
|
-
|
128
|
-
|
129
|
-
|
119
|
+
# `height_updated` handler for the text node
|
120
|
+
#
|
121
|
+
# Since panels are just blocks, and have no insight to the height of their children
|
122
|
+
# I will update the height of this post dynamically based on the height of the rendered text
|
123
|
+
def text_height_updated(height)
|
124
|
+
# tic tac toe height + about node height || content height + padding
|
125
|
+
@height = 400 + [80, height].max + 80
|
126
|
+
end
|
130
127
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# they are used by the layout rendered to compute the canvas
|
135
|
-
#
|
136
|
-
# We will set it after the block is updated on each iteration of the event loop
|
137
|
-
def after_updated
|
138
|
-
node.meta.set_prop(:height, @height) if @height != node.meta.get_prop(:height)
|
139
|
-
end
|
140
|
-
end
|
128
|
+
def on_mounted
|
129
|
+
text_height_updated(0)
|
130
|
+
end
|
141
131
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
[style]
|
152
|
-
main {
|
153
|
-
background: rgb(34, 38, 57);
|
154
|
-
}
|
155
|
-
|
156
|
-
panelStyle {
|
157
|
-
scroll_color: rgb(30, 30, 163);
|
158
|
-
scroll_background: rgb(15, 11, 48);
|
159
|
-
}
|
160
|
-
EOF
|
161
|
-
|
162
|
-
template <<~EOF
|
163
|
-
[template]
|
164
|
-
vblock { ...main }
|
165
|
-
stock { height="150" }
|
166
|
-
panel { ...panelStyle }
|
167
|
-
[for="post in posts"]
|
168
|
-
post {
|
169
|
-
:key="key(post)"
|
170
|
-
:index="index"
|
171
|
-
:entry="post"
|
172
|
-
}
|
173
|
-
EOF
|
174
|
-
|
175
|
-
# keys map to template node names
|
176
|
-
# values map to blocks
|
177
|
-
uses(
|
178
|
-
hblock: Hokusai::Blocks::Hblock,
|
179
|
-
vblock: Hokusai::Blocks::Vblock,
|
180
|
-
label: Hokusai::Blocks::Label,
|
181
|
-
panel: Hokusai::Blocks::Panel,
|
182
|
-
text: Hokusai::Blocks::Text,
|
183
|
-
stock: StockDecider::App, # some app to chart stock prices for a friend
|
184
|
-
post: Post,
|
185
|
-
)
|
186
|
-
|
187
|
-
# methods can be accessed in computed props
|
188
|
-
attr_accessor :posts
|
189
|
-
|
190
|
-
# initializer override
|
191
|
-
def initialize(**args)
|
192
|
-
@posts = []
|
193
|
-
|
194
|
-
super
|
132
|
+
# Lifecycle hook
|
133
|
+
#
|
134
|
+
# `height` and `width` are the only special props
|
135
|
+
# they are used by the layout rendered to compute the canvas
|
136
|
+
#
|
137
|
+
# We will set it after the block is updated on each iteration of the event loop
|
138
|
+
def after_updated
|
139
|
+
node.meta.set_prop(:height, @height) if @height != node.meta.get_prop(:height)
|
140
|
+
end
|
195
141
|
end
|
196
142
|
|
197
|
-
#
|
198
|
-
|
199
|
-
entry.id
|
200
|
-
end
|
143
|
+
# Plain old ruby class
|
144
|
+
PostEntry = Struct.new(:id, :title, :content)
|
201
145
|
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
146
|
+
# Entrypoint Block
|
147
|
+
# Nothing is special about this block - any block can be used as an entrypoint
|
148
|
+
#
|
149
|
+
# NOTE: Scrollbars, panels, and other basic functions are also plain blocks
|
150
|
+
class App < Hokusai::Block
|
151
|
+
style <<~EOF
|
152
|
+
[style]
|
153
|
+
main {
|
154
|
+
background: rgb(34, 38, 57);
|
155
|
+
}
|
156
|
+
|
157
|
+
panelStyle {
|
158
|
+
scroll_color: rgb(30, 30, 163);
|
159
|
+
scroll_background: rgb(15, 11, 48);
|
160
|
+
}
|
161
|
+
EOF
|
162
|
+
|
163
|
+
template <<~EOF
|
164
|
+
[template]
|
165
|
+
vblock { ...main }
|
166
|
+
stock { height="150" }
|
167
|
+
panel { ...panelStyle }
|
168
|
+
[for="post in posts"]
|
169
|
+
post {
|
170
|
+
:key="key(post)"
|
171
|
+
:index="index"
|
172
|
+
:entry="post"
|
173
|
+
}
|
174
|
+
EOF
|
175
|
+
|
176
|
+
# keys map to template node names
|
177
|
+
# values map to blocks
|
178
|
+
uses(
|
179
|
+
hblock: Hokusai::Blocks::Hblock,
|
180
|
+
vblock: Hokusai::Blocks::Vblock,
|
181
|
+
label: Hokusai::Blocks::Label,
|
182
|
+
panel: Hokusai::Blocks::Panel,
|
183
|
+
text: Hokusai::Blocks::Text,
|
184
|
+
stock: StockDecider::App, # some app to chart stock prices for a friend
|
185
|
+
post: Post,
|
186
|
+
)
|
187
|
+
|
188
|
+
# methods can be accessed in computed props
|
189
|
+
attr_accessor :posts
|
190
|
+
|
191
|
+
# initializer override
|
192
|
+
def initialize(**args)
|
193
|
+
@posts = []
|
194
|
+
|
195
|
+
super
|
196
|
+
end
|
210
197
|
|
211
|
-
|
198
|
+
# loop state can be passed to methods
|
199
|
+
def key(entry)
|
200
|
+
entry.id
|
201
|
+
end
|
212
202
|
|
213
|
-
#
|
214
|
-
#
|
215
|
-
#
|
216
|
-
|
217
|
-
#
|
218
|
-
|
203
|
+
# lifecycle hook
|
204
|
+
# `on_mounted`
|
205
|
+
# `before_updated`
|
206
|
+
# `after_updated`
|
207
|
+
# `on_destroy`
|
208
|
+
def on_mounted
|
209
|
+
uri = URI("https://jsonplaceholder.typicode.com/posts")
|
210
|
+
res = JSON.parse(Net::HTTP.get(uri), symbolize_names: true)
|
211
|
+
|
212
|
+
self.posts = res.map { |json| PostEntry.new(json[:id], json[:title], json[:body]) }.freeze
|
213
|
+
|
214
|
+
# can access details about this block
|
215
|
+
#
|
216
|
+
# get the node count
|
217
|
+
puts node.meta.node_count
|
218
|
+
# show the ast
|
219
|
+
puts dump
|
220
|
+
end
|
219
221
|
end
|
220
222
|
end
|
221
223
|
end
|
222
224
|
|
223
225
|
# Backends include Raylib and SDL2
|
224
|
-
Hokusai::Backends::SDLBackend.run(Demo::App) do |config|
|
226
|
+
Hokusai::Backends::SDLBackend.run(Demos::Demo::App) do |config|
|
225
227
|
# backend specific configuration
|
226
228
|
config.width = 600
|
227
229
|
config.height = 500
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Demos
|
2
|
+
module Forum
|
3
|
+
class FileBlock < Hokusai::Block
|
4
|
+
style <<~EOF
|
5
|
+
[style]
|
6
|
+
file {
|
7
|
+
height: 20;
|
8
|
+
}
|
9
|
+
|
10
|
+
icon {
|
11
|
+
width: 14;
|
12
|
+
height: 14;
|
13
|
+
}
|
14
|
+
|
15
|
+
itemStyle {
|
16
|
+
color: rgb(247, 247, 247);
|
17
|
+
size: 14;
|
18
|
+
padding: padding(3.0, 0.0, 3.0, 5.0);
|
19
|
+
}
|
20
|
+
EOF
|
21
|
+
|
22
|
+
template <<~EOF
|
23
|
+
[template]
|
24
|
+
hblock.file { ...file :background="compute_background" }
|
25
|
+
image { ...icon :source="item_image" }
|
26
|
+
text { ...itemStyle :content="item.name" }
|
27
|
+
EOF
|
28
|
+
|
29
|
+
computed! :item
|
30
|
+
computed! :index
|
31
|
+
|
32
|
+
uses(
|
33
|
+
hblock: Hokusai::Blocks::Hblock,
|
34
|
+
image: Hokusai::Blocks::Image,
|
35
|
+
text: Hokusai::Blocks::Text,
|
36
|
+
)
|
37
|
+
|
38
|
+
def compute_background
|
39
|
+
index.odd? ? nil : Hokusai::Color.new(255, 255, 255,20)
|
40
|
+
end
|
41
|
+
|
42
|
+
def item_image
|
43
|
+
asset = {
|
44
|
+
audio: "audio-x-generic.png",
|
45
|
+
image: "image-x-generic.png",
|
46
|
+
app: "package-x-generic.png",
|
47
|
+
video: "video-x-generic.png",
|
48
|
+
}[item.type] || "text-x-generic.png"
|
49
|
+
|
50
|
+
"#{__dir__}/../assets/icons/#{asset}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Demos
|
2
|
+
module Forum
|
3
|
+
class MusicBlock < Hokusai::Block
|
4
|
+
template <<~EOF
|
5
|
+
[template]
|
6
|
+
empty {
|
7
|
+
cursor="pointer"
|
8
|
+
@click="update_player"
|
9
|
+
}
|
10
|
+
EOF
|
11
|
+
|
12
|
+
uses(
|
13
|
+
hblock: Hokusai::Blocks::Hblock,
|
14
|
+
empty: Hokusai::Blocks::Empty
|
15
|
+
)
|
16
|
+
|
17
|
+
attr_accessor :started, :duration, :ticks, :head
|
18
|
+
|
19
|
+
def initialize(**args)
|
20
|
+
super
|
21
|
+
@started = false
|
22
|
+
@duration = 300
|
23
|
+
@head = 0
|
24
|
+
@ticks = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_player(event)
|
28
|
+
self.started = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def control
|
32
|
+
started ? "#{__dir__}/../assets/icons/media-playback-pause.png" : "#{__dir__}/../assets/icons/media-playback-start.png"
|
33
|
+
end
|
34
|
+
|
35
|
+
def render(canvas)
|
36
|
+
if started
|
37
|
+
if self.ticks >= 60
|
38
|
+
self.head += 1
|
39
|
+
self.ticks = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
if head >= duration
|
43
|
+
self.started = false
|
44
|
+
self.head = 0
|
45
|
+
self.ticks = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
self.ticks += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
draw do
|
52
|
+
start = canvas.x + 20
|
53
|
+
canvas.y += 6.0
|
54
|
+
padding = 4.0
|
55
|
+
|
56
|
+
# draw controls
|
57
|
+
image(control, canvas.x, canvas.y, 15.0, 15.0)
|
58
|
+
|
59
|
+
# draw line
|
60
|
+
rect(start, canvas.y + 3.0 + padding, canvas.width, 2.0) do |command|
|
61
|
+
command.color = Hokusai::Color.new(244,244,244)
|
62
|
+
end
|
63
|
+
|
64
|
+
# draw the playhead
|
65
|
+
ix = head * canvas.width / duration
|
66
|
+
rect(start + ix, canvas.y + padding, 2.0, 10.0) do |command|
|
67
|
+
command.color = Hokusai::Color.new(244,244,244)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
yield canvas
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Demos
|
2
|
+
module Forum
|
3
|
+
class PostBlock < Hokusai::Block
|
4
|
+
style <<~EOF
|
5
|
+
[style]
|
6
|
+
authorBlock {
|
7
|
+
width: 100;
|
8
|
+
}
|
9
|
+
|
10
|
+
authorLabel {
|
11
|
+
size: 12;
|
12
|
+
color: rgb(244,244,244);
|
13
|
+
}
|
14
|
+
|
15
|
+
authorImage {
|
16
|
+
width: 60;
|
17
|
+
height: 60;
|
18
|
+
}
|
19
|
+
|
20
|
+
outlineBottom {
|
21
|
+
outline: outline(0.0, 0.0, 2.0, 0.0);
|
22
|
+
outline_color: rgb(244, 244, 244);
|
23
|
+
}
|
24
|
+
|
25
|
+
postText {
|
26
|
+
color: rgb(244, 244, 244);
|
27
|
+
}
|
28
|
+
|
29
|
+
postTitle {
|
30
|
+
size: 35;
|
31
|
+
markdown: false;
|
32
|
+
font: "dohyeon";
|
33
|
+
padding: padding(0.0, 0.0, 0.0, 0.0);
|
34
|
+
}
|
35
|
+
|
36
|
+
postBody {
|
37
|
+
size: 17;
|
38
|
+
markdown: true;
|
39
|
+
}
|
40
|
+
|
41
|
+
actions {
|
42
|
+
height: 30;
|
43
|
+
}
|
44
|
+
|
45
|
+
postDate {
|
46
|
+
size: 14;
|
47
|
+
}
|
48
|
+
|
49
|
+
viewLabel {
|
50
|
+
color: rgb(231, 236, 154);
|
51
|
+
content: "View Replies (20)";
|
52
|
+
size: 15;
|
53
|
+
}
|
54
|
+
|
55
|
+
rightPad {
|
56
|
+
padding: padding(0.0, 20.0, 20.0, 0.0);
|
57
|
+
cursor: "pointer";
|
58
|
+
}
|
59
|
+
|
60
|
+
replyLabel {
|
61
|
+
color: rgb(231, 236, 154);
|
62
|
+
content: "Reply";
|
63
|
+
size: 15;
|
64
|
+
}
|
65
|
+
EOF
|
66
|
+
|
67
|
+
template <<~EOF
|
68
|
+
[template]
|
69
|
+
hblock
|
70
|
+
vblock#author { ...authorBlock }
|
71
|
+
image {
|
72
|
+
:source="post.author_image"
|
73
|
+
...authorImage
|
74
|
+
}
|
75
|
+
label {
|
76
|
+
:content="post.author_name"
|
77
|
+
...authorLabel
|
78
|
+
}
|
79
|
+
vblock#post
|
80
|
+
text.title {
|
81
|
+
...postText
|
82
|
+
...postTitle
|
83
|
+
:content="post.title"
|
84
|
+
:height="post_title_height"
|
85
|
+
@height_updated="post_title_height_updated"
|
86
|
+
}
|
87
|
+
text.date {
|
88
|
+
...postText
|
89
|
+
...postDate
|
90
|
+
:content="post.date"
|
91
|
+
}
|
92
|
+
text.body {
|
93
|
+
...postText
|
94
|
+
...postBody
|
95
|
+
:content="post.body"
|
96
|
+
:height="post_height"
|
97
|
+
@height_updated="post_height_updated"
|
98
|
+
}
|
99
|
+
[if="post_has_app"]
|
100
|
+
variable { :script="post.app" @height_updated="app_height_updated"}
|
101
|
+
hblock.actions {
|
102
|
+
...actions
|
103
|
+
}
|
104
|
+
label { ...viewLabel ...rightPad }
|
105
|
+
label { ...replyLabel ...rightPad }
|
106
|
+
EOF
|
107
|
+
|
108
|
+
uses(
|
109
|
+
vblock: Hokusai::Blocks::Vblock,
|
110
|
+
hblock: Hokusai::Blocks::Hblock,
|
111
|
+
text: Hokusai::Blocks::Text,
|
112
|
+
image: Hokusai::Blocks::Image,
|
113
|
+
label: Hokusai::Blocks::Label,
|
114
|
+
hblock: Hokusai::Blocks::Hblock,
|
115
|
+
variable: Hokusai::Blocks::Variable
|
116
|
+
)
|
117
|
+
|
118
|
+
computed! :post
|
119
|
+
computed! :index
|
120
|
+
|
121
|
+
attr_reader :post_height, :post_title_height, :app_height
|
122
|
+
|
123
|
+
def post_has_app
|
124
|
+
!post.app.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
def app_height_updated(height)
|
128
|
+
@app_height = height
|
129
|
+
end
|
130
|
+
|
131
|
+
def post_title_height_updated(height)
|
132
|
+
@post_title_height = height
|
133
|
+
end
|
134
|
+
|
135
|
+
def post_height_updated(height)
|
136
|
+
@post_height = height + 50
|
137
|
+
end
|
138
|
+
|
139
|
+
def after_updated
|
140
|
+
height = (@post_height || 100) + (@post_title_height || 90) + (@app_height || 0) + 50
|
141
|
+
node.meta.set_prop(:height, height)
|
142
|
+
emit("height_updated", height, index)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|