Last active
December 14, 2024 17:50
-
-
Save skinnyjames/c8388ccbbd60c53c0d86df258a7924cb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# dependencies | |
require_relative "../src/hokusai" | |
require_relative "../src/hokusai/backends/sdl2" | |
require_relative "../src/hokusai/backends/raylib" | |
require_relative "./stock" | |
require_relative "./tic_tac_toe" | |
# can use ruby stdlib | |
# or any library in the ruby ecosystem | |
require "json" | |
require "net/http" | |
module Demo | |
# Block that represents a singular post | |
# | |
# Displays post content, and throws in a game of Tic Tac Toe for good measure. | |
# | |
# Takes up full width on small viewport, centers on large viewport | |
# Duplication in the template can easily be extracted into a separate block | |
class Post < Hokusai::Block | |
template <<~EOF | |
[template] | |
vblock | |
hblock | |
vblock.about { width="80" :background="about_background" } | |
image { | |
width="80" | |
height="80" | |
:source="about_image_source" | |
} | |
label { :content="post_author_name" size="9" } | |
vblock.content | |
label { :content="post_title" size="20" } | |
text { | |
:content="post_body" | |
:padding="text_padding" | |
size="17 | |
@height_updated="text_height_updated" | |
} | |
tic_tac_toe { height="400" width="400" } | |
EOF | |
# Mandatory props | |
computed! :entry | |
computed! :index | |
# injects the screen type from the provisioned value | |
# and aliases it to :media_type | |
inject :screen_type, :media_type | |
uses( | |
hblock: Hokusai::Blocks::Hblock, | |
vblock: Hokusai::Blocks::Vblock, | |
image: Hokusai::Blocks::Image, | |
text: Hokusai::Blocks::Text, | |
label: Hokusai::Blocks::Label, | |
empty: Hokusai::Blocks::Empty, | |
tic_tac_toe: TicTacToe::App | |
) | |
# This block is rendered inside a scrollable panel | |
# in between a `clip begin` and `clip end` command | |
# | |
# `Hokusai.can_render(canvas) will tell us if this block will even be visible` | |
# If not visible, I won't waste memory / cpu to render | |
def render(canvas) | |
if Hokusai.can_render(canvas) | |
yield(canvas) | |
end | |
end | |
def post_index | |
index.to_s | |
end | |
def post_body | |
entry.content | |
end | |
def post_title | |
@post_title ||= "#{entry.title.upcase} - #{DateTime.now.strftime("%m/%d/%Y %H:%M %p")}" | |
end | |
def post_author_name | |
@post_author_name ||= entry.id.even? ? "Adeline" : "Skinnyjames" | |
end | |
def about_image_source | |
@about_image_source ||= entry.id.even? ? "#{__dir__}/assets/addy.png" : "#{__dir__}/assets/baby_sean.png" | |
end | |
# Cache the color so that it isn't spinning up new objects at `O(n) complexity` | |
# | |
# TODO: extract styling / prop declaration into separate template | |
def about_background | |
@author_background ||= Hokusai::Color.new(155,155,155,20) | |
end | |
# Cache the text padding | |
def text_padding | |
@text_padding ||= Hokusai::Padding.new(20, 5, 20, 5) | |
end | |
# `height_updated` handler for the text node | |
# | |
# Since panels are just blocks, and have no insight to the height of their children | |
# I will update the height of this post dynamically based on the height of the rendered text | |
def text_height_updated(height) | |
# tic tac toe height + about node height || content height + padding | |
@height = 400 + [80, height].max + 40 | |
end | |
def on_mounted | |
text_height_updated(0) | |
end | |
# Lifecycle hook | |
# | |
# `height` and `width` are the only special props | |
# they are used by the layout rendered to compute the canvas | |
# | |
# We will set it after the block is updated on each iteration of the event loop | |
def after_updated | |
node.meta.set_prop(:height, @height) if @height != node.meta.get_prop(:height) | |
end | |
end | |
# Plain old ruby class | |
PostEntry = Struct.new(:id, :title, :content) | |
# Entrypoint Block | |
# Nothing is special about this block - any block can be used as an entrypoint | |
# | |
# NOTE: Scrollbars, panels, and other basic functions are also plain blocks | |
class App < Hokusai::Block | |
template <<~EOF | |
[template] | |
vblock { background="246,255,195"} | |
stock { height="150" } | |
panel { scroll_color="255,246,116" scroll_background="0,0,0,30"} | |
[for="post in posts"] | |
post { | |
:key="key(post)" | |
:index="index" | |
:entry="post" | |
} | |
EOF | |
# keys map to template node names | |
# values map to blocks | |
uses( | |
hblock: Hokusai::Blocks::Hblock, | |
vblock: Hokusai::Blocks::Vblock, | |
label: Hokusai::Blocks::Label, | |
panel: Hokusai::Blocks::Panel, | |
text: Hokusai::Blocks::Text, | |
stock: StockDecider::App, # some app to chart stock prices for a friend | |
post: Post, | |
) | |
# methods can be accessed in computed props | |
attr_accessor :posts | |
# initializer override | |
def initialize(**args) | |
@posts = [] | |
super | |
end | |
# loop state can be passed to methods | |
def key(entry) | |
entry.id | |
end | |
# lifecycle hook | |
# `on_mounted` | |
# `before_updated` | |
# `after_updated` | |
# `on_destroy` | |
def on_mounted | |
uri = URI("https://jsonplaceholder.typicode.com/posts") | |
res = JSON.parse(Net::HTTP.get(uri), symbolize_names: true) | |
self.posts = res.map { |json| PostEntry.new(json[:id], json[:title], json[:body]) }.freeze | |
# can access details about this block | |
# | |
# get the node count | |
puts node.meta.node_count | |
# show the ast | |
puts dump | |
end | |
end | |
end | |
# Backends include Raylib and SDL2 | |
Hokusai::Backends::RaylibBackend.run(Demo::App) do |config| | |
# backend specific configuration | |
config.width = 600 | |
config.height = 500 | |
config.title = "Demo Application" | |
config.after_load do | |
# most heavy logic, including text wrapping calculations are implemented in C | |
font = Hokusai::Backends::RaylibBackend::Font.from("#{__dir__}/assets/OpenSans-Regular.ttf") | |
Hokusai.fonts.register "opensans", font | |
Hokusai.fonts.activate "opensans" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment