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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +4 -0
  4. data/README.md +2 -0
  5. data/ast/src/core/hml.c +9 -9
  6. data/ast/src/core/text.c +1 -3
  7. data/ast/test/text.c +3 -3
  8. data/docs.sh +29 -0
  9. data/ext/extconf.rb +50 -14
  10. data/grammar/corpus/1_document.txt +24 -0
  11. data/grammar/corpus/6_styles.txt +23 -0
  12. data/grammar/grammar.js +4 -4
  13. data/grammar/src/grammar.json +19 -19
  14. data/grammar/src/parser.c +1904 -1956
  15. data/grammar/test.nml +10 -8
  16. data/hokusai.gemspec +2 -1
  17. data/ui/examples/assets/Delius-Regular.ttf +0 -0
  18. data/ui/examples/assets/DoHyeon.ttf +0 -0
  19. data/ui/examples/assets/Inter-Regular.ttf +0 -0
  20. data/ui/examples/assets/ernest.gif +0 -0
  21. data/ui/examples/assets/icons/audio-x-generic.png +0 -0
  22. data/ui/examples/assets/icons/image-x-generic.png +0 -0
  23. data/ui/examples/assets/icons/media-playback-pause.png +0 -0
  24. data/ui/examples/assets/icons/media-playback-start.png +0 -0
  25. data/ui/examples/assets/icons/media-playback-stop.png +0 -0
  26. data/ui/examples/assets/icons/package-x-generic.png +0 -0
  27. data/ui/examples/assets/icons/text-x-generic.png +0 -0
  28. data/ui/examples/assets/icons/video-x-generic.png +0 -0
  29. data/ui/examples/buddy.rb +16 -14
  30. data/ui/examples/clock.rb +38 -36
  31. data/ui/examples/counter.rb +100 -98
  32. data/ui/examples/dynamic.rb +115 -113
  33. data/ui/examples/foobar.rb +189 -187
  34. data/ui/examples/forum/file.rb +54 -0
  35. data/ui/examples/forum/music.rb +76 -0
  36. data/ui/examples/forum/post.rb +146 -0
  37. data/ui/examples/forum.rb +198 -0
  38. data/ui/examples/spreadsheet/csv.rb +261 -0
  39. data/ui/examples/spreadsheet.rb +138 -0
  40. data/ui/examples/stock.rb +86 -92
  41. data/ui/examples/stock_decider/option.rb +1 -1
  42. data/ui/examples/tic_tac_toe.rb +193 -191
  43. data/ui/lib/lib_hokusai.rb +0 -1
  44. data/ui/src/hokusai/ast.rb +42 -43
  45. data/ui/src/hokusai/backends/raylib/font.rb +1 -2
  46. data/ui/src/hokusai/backends/raylib.rb +16 -7
  47. data/ui/src/hokusai/backends/sdl2/font.rb +13 -9
  48. data/ui/src/hokusai/backends/sdl2.rb +5 -5
  49. data/ui/src/hokusai/block.rb +7 -0
  50. data/ui/src/hokusai/blocks/hblock.rb +2 -2
  51. data/ui/src/hokusai/blocks/image.rb +5 -1
  52. data/ui/src/hokusai/blocks/input.rb +17 -0
  53. data/ui/src/hokusai/blocks/label.rb +5 -2
  54. data/ui/src/hokusai/blocks/text.rb +10 -5
  55. data/ui/src/hokusai/blocks/titlebar/osx.rb +4 -4
  56. data/ui/src/hokusai/blocks/variable.rb +33 -0
  57. data/ui/src/hokusai/blocks/vblock.rb +1 -1
  58. data/ui/src/hokusai/commands/rect.rb +4 -4
  59. data/ui/src/hokusai/commands.rb +33 -31
  60. data/ui/src/hokusai/diff.rb +11 -0
  61. data/ui/src/hokusai/event.rb +19 -5
  62. data/ui/src/hokusai/events/mouse.rb +9 -1
  63. data/ui/src/hokusai/font.rb +60 -0
  64. data/ui/src/hokusai/meta.rb +10 -16
  65. data/ui/src/hokusai/node.rb +1 -1
  66. data/ui/src/hokusai/painter.rb +1 -2
  67. data/ui/src/hokusai/util/clamping_iterator.rb +5 -6
  68. data/ui/src/hokusai.rb +36 -4
  69. metadata +37 -3
@@ -12,216 +12,218 @@ require_relative "./tic_tac_toe"
12
12
  require "json"
13
13
  require "net/http"
14
14
 
15
- module Demo
16
- # Block that represents a singular post
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
- # `Hokusai.can_render(canvas) will tell us if this block will even be visible`
75
- # If not visible, I won't waste memory / cpu to render
76
- def render(canvas)
77
- if Hokusai.can_render(canvas)
78
- yield(canvas)
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
- def small_viewport
83
- media_type == :small
84
- end
85
-
86
- def post_index
87
- index.to_s
88
- end
83
+ def small_viewport
84
+ media_type == :small
85
+ end
89
86
 
90
- def post_body
91
- entry.content
92
- end
87
+ def post_index
88
+ index.to_s
89
+ end
93
90
 
94
- def post_title
95
- @post_title ||= "_#{entry.title.upcase}_ - #{DateTime.now.strftime("%m/%d/%Y %H:%M %p")}"
96
- end
91
+ def post_body
92
+ entry.content
93
+ end
97
94
 
98
- def post_author_name
99
- @post_author_name ||= entry.id.even? ? "Adeline" : "Skinnyjames"
100
- end
95
+ def post_title
96
+ @post_title ||= "_#{entry.title.upcase}_ - #{DateTime.now.strftime("%m/%d/%Y %H:%M %p")}"
97
+ end
101
98
 
102
- def about_image_source
103
- @about_image_source ||= entry.id.even? ? "#{__dir__}/assets/addy.png" : "#{__dir__}/assets/baby_sean.png"
104
- end
99
+ def post_author_name
100
+ @post_author_name ||= entry.id.even? ? "Adeline" : "Skinnyjames"
101
+ end
105
102
 
106
- # Cache the color so that it isn't recomputing at `O(n) complexity`
107
- #
108
- # TODO: extract styling / prop declaration into separate template
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
- # Cache the text padding
114
- def text_padding
115
- @text_padding ||= Hokusai::Padding.new(20, 5, 20, 5)
116
- end
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
- # `height_updated` handler for the text node
119
- #
120
- # Since panels are just blocks, and have no insight to the height of their children
121
- # I will update the height of this post dynamically based on the height of the rendered text
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
- def on_mounted
128
- text_height_updated(0)
129
- end
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
- # Lifecycle hook
132
- #
133
- # `height` and `width` are the only special props
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
- # Plain old ruby class
143
- PostEntry = Struct.new(:id, :title, :content)
144
-
145
- # Entrypoint Block
146
- # Nothing is special about this block - any block can be used as an entrypoint
147
- #
148
- # NOTE: Scrollbars, panels, and other basic functions are also plain blocks
149
- class App < Hokusai::Block
150
- style <<~EOF
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
- # loop state can be passed to methods
198
- def key(entry)
199
- entry.id
200
- end
143
+ # Plain old ruby class
144
+ PostEntry = Struct.new(:id, :title, :content)
201
145
 
202
- # lifecycle hook
203
- # `on_mounted`
204
- # `before_updated`
205
- # `after_updated`
206
- # `on_destroy`
207
- def on_mounted
208
- uri = URI("https://jsonplaceholder.typicode.com/posts")
209
- res = JSON.parse(Net::HTTP.get(uri), symbolize_names: true)
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
- self.posts = res.map { |json| PostEntry.new(json[:id], json[:title], json[:body]) }.freeze
198
+ # loop state can be passed to methods
199
+ def key(entry)
200
+ entry.id
201
+ end
212
202
 
213
- # can access details about this block
214
- #
215
- # get the node count
216
- puts node.meta.node_count
217
- # show the ast
218
- puts dump
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