Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,7 @@ build-iPhoneSimulator/

*.dll
*.so
*.exp
*.lib

.vscode
Binary file added .ruby-version
Binary file not shown.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@

#### Windows

I recommend to install Ruby via [Scoop](https://scoop.sh/), then
I recommend to install Ruby via [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/):

`winget install RubyInstallerTeam.RubyWithDevKit.3.2`

The reason why you need the dev kit is that the event machine gem needs to be compiled on the target machine.

Then install the dependencies (you likely need to close all terminals first):

- `gem install ffi`
- `gem install eventmachine`
- `gem install rx`

`RUBY_DLL_PATH` must be set:
`RUBY_DLL_PATH` must be set, e.g.:

`$env:RUBY_DLL_PATH="C:\dev\xframes-ruby"`

For convenience, you may launch `main.bat` or `main.ps1` depending on whether you are using a regular command line or PowerShell.

#### Linux

- `sudo apt install ruby-full`
- `sudo gem install ffi`
- `sudo gem install eventmachine`
- `sudo gem install rx`

### Run the application

Expand All @@ -34,4 +44,3 @@ Windows 11
Raspberry Pi 5

![image](https://github.com/user-attachments/assets/190f8603-a6db-45c6-a5f0-cfd4dc1b87e2)

4 changes: 4 additions & 0 deletions main.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@echo off
set RUBY_DLL_PATH=%CD%
echo RUBY_DLL_PATH set to %RUBY_DLL_PATH%
ruby main.rb
3 changes: 3 additions & 0 deletions main.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$env:RUBY_DLL_PATH = Get-Location
Write-Output "RUBY_DLL_PATH set to $env:RUBY_DLL_PATH"
ruby main.rb
174 changes: 47 additions & 127 deletions main.rb
Original file line number Diff line number Diff line change
@@ -1,63 +1,13 @@
require 'ffi'
require 'json'
require 'thread'
require 'concurrent-ruby'
require 'eventmachine'

ImGuiCol = {
Text: 0,
TextDisabled: 1,
WindowBg: 2,
ChildBg: 3,
PopupBg: 4,
Border: 5,
BorderShadow: 6,
FrameBg: 7,
FrameBgHovered: 8,
FrameBgActive: 9,
TitleBg: 10,
TitleBgActive: 11,
TitleBgCollapsed: 12,
MenuBarBg: 13,
ScrollbarBg: 14,
ScrollbarGrab: 15,
ScrollbarGrabHovered: 16,
ScrollbarGrabActive: 17,
CheckMark: 18,
SliderGrab: 19,
SliderGrabActive: 20,
Button: 21,
ButtonHovered: 22,
ButtonActive: 23,
Header: 24,
HeaderHovered: 25,
HeaderActive: 26,
Separator: 27,
SeparatorHovered: 28,
SeparatorActive: 29,
ResizeGrip: 30,
ResizeGripHovered: 31,
ResizeGripActive: 32,
Tab: 33,
TabHovered: 34,
TabActive: 35,
TabUnfocused: 36,
TabUnfocusedActive: 37,
PlotLines: 38,
PlotLinesHovered: 39,
PlotHistogram: 40,
PlotHistogramHovered: 41,
TableHeaderBg: 42,
TableBorderStrong: 43,
TableBorderLight: 44,
TableRowBg: 45,
TableRowBgAlt: 46,
TextSelectedBg: 47,
DragDropTarget: 48,
NavHighlight: 49,
NavWindowingHighlight: 50,
NavWindowingDimBg: 51,
ModalWindowDimBg: 52,
COUNT: 53
}
require_relative 'theme'
require_relative 'sampleapp'
require_relative 'services'
require_relative 'treetraversal'
require_relative 'xframes'

# Colors for theme generation
theme2Colors = {
Expand Down Expand Up @@ -145,89 +95,59 @@
theme_json = JSON.pretty_generate(theme2)

class Node
attr_accessor :id, :root

def initialize(id, root)
@type = 'node'
@id = id
@root = root
end

def to_json(*options)
{
type: @type,
id: @id,
root: @root
}.to_json(*options)
end
end

class UnformattedText
attr_accessor :id, :text

def initialize(id, text)
@type = 'unformatted-text'
@id = id
@text = text
end

def to_json(*options)
{
type: @type,
id: @id,
text: @text
}.to_json(*options)
end
end

attr_accessor :id, :root

module XFrames
extend FFI::Library
if RUBY_PLATFORM =~ /win32|mingw|cygwin/
ffi_lib './xframesshared.dll'
else
ffi_lib './libxframesshared.so'
def initialize(id, root)
@type = 'node'
@id = id
@root = root
end

# Define callback types
callback :OnInitCb, [:pointer], :void
callback :OnTextChangedCb, [:int, :string], :void
callback :OnComboChangedCb, [:int, :int], :void
callback :OnNumericValueChangedCb, [:int, :float], :void
callback :OnBooleanValueChangedCb, [:int, :int], :void
callback :OnMultipleNumericValuesChangedCb, [:int, :pointer, :int], :void
callback :OnClickCb, [:int], :void
def to_json(*options)
{
type: @type,
id: @id,
root: @root
}.to_json(*options)
end
end

attach_function :init, [
:string, # assetsBasePath
:string, # rawFontDefinitions
:string, # rawStyleOverrideDefinitions
:OnInitCb,
:OnTextChangedCb,
:OnComboChangedCb,
:OnNumericValueChangedCb,
:OnBooleanValueChangedCb,
:OnMultipleNumericValuesChangedCb,
:OnClickCb
], :void
class UnformattedText
attr_accessor :id, :text

attach_function :setElement, [:string], :void
def initialize(id, text)
@type = 'unformatted-text'
@id = id
@text = text
end

attach_function :setChildren, [:int, :string], :void
def to_json(*options)
{
type: @type,
id: @id,
text: @text
}.to_json(*options)
end
end

service = WidgetRegistrationService.new
shadow_node_traversal_helper = ShadowNodeTraversalHelper.new(service)

on_init = FFI::Function.new(:void, []) do
puts "OnInit called!"
# Create an Async task using concurrent-ruby's Async
# Concurrent::Async.perform do
# Thread.main do
puts "OnInit called!"

node = Node.new(0, true)
unformatted_text = UnformattedText.new(1, "Hello, world")
root = Root.new()

children_ids = [1]
puts "After root creation"

XFrames.setElement(node.to_json)
XFrames.setElement(unformatted_text.to_json)
shadow_node_traversal_helper.traverse_tree(root)

XFrames.setChildren(0, children_ids.to_json)
puts "After tree"
# end
# end
end

on_text_changed = FFI::Function.new(:void, [:int, :string]) do |id, text|
Expand Down
121 changes: 121 additions & 0 deletions sampleapp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'rx'
require_relative 'theme'
require_relative 'widgetnode'
require 'thread'
require 'concurrent-ruby'

Comment on lines +1 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the dependencies are properly installed.

Please ensure that all required gems are listed in the project's Gemfile:

gem 'rx'
gem 'concurrent-ruby'

Run this script to check the dependencies:


🏁 Script executed:

#!/bin/bash
# Description: Check if required gems are installed and their versions.

# Check if gems are installed
gem list | grep -E "^(rx|concurrent-ruby)"

# Check latest versions on RubyGems
curl -s https://rubygems.org/api/v1/versions/rx.json | jq '.[0].number'
curl -s https://rubygems.org/api/v1/versions/concurrent-ruby.json | jq '.[0].number'

Length of output: 245


Missing Dependency: "rx" gem Not Installed

• The dependency check confirms that "concurrent-ruby" (v1.3.5) is properly installed.
• However, the gem "rx" required by the file (via require 'rx') is missing from the installed gems list.
• Please ensure that the project's Gemfile includes gem 'rx' (expected latest version "0.0.3") and install it accordingly.

class TodoItem
attr_accessor :text, :done

def initialize(text, done)
@text = text
@done = done
end
end

class AppState
attr_accessor :todo_text, :todo_items

def initialize(todo_text, todo_items)
@todo_text = todo_text
@todo_items = todo_items
end
end

$sample_app_state = Rx::BehaviorSubject.new(AppState.new("", []))
Comment on lines +16 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Encapsulate application state within a service class.

Using a global variable ($sample_app_state) for state management is not recommended. Consider encapsulating the state within a service class:

-$sample_app_state = Rx::BehaviorSubject.new(AppState.new("", []))

+class TodoService
+  def self.instance
+    @instance ||= new
+  end
+
+  def initialize
+    @state = Rx::BehaviorSubject.new(AppState.new("", []))
+  end
+
+  def state
+    @state
+  end
+
+  private_class_method :new
+end

This provides better encapsulation and follows the Singleton pattern.


def on_click
new_todo_item = TodoItem.new("New Todo", false)

current_state = $sample_app_state.value

new_state = AppState.new(
current_state.todo_text,
current_state.todo_items + [new_todo_item]
)

$sample_app_state.on_next(new_state)
end

$text_style = WidgetStyle.new(
style: WidgetStyleDef.new(
style_rules: StyleRules.new(
font: FontDef.new(name: "roboto-regular", size: 32)
)
)
)

$button_style = WidgetStyle.new(
style: WidgetStyleDef.new(
style_rules: StyleRules.new(
font: FontDef.new(name: "roboto-regular", size: 32)
),
layout: YogaStyle.new(
width: "50%",
padding: {Edge[:Vertical] => 10},
margin: {Edge[:Left] => 140}
)
)
)

class App < BaseComponent
def initialize
super({})

@sample_app_state = Rx::BehaviorSubject.new(AppState.new("", []))

@props.on_next({
"todo_text" => "",
"todo_items" => []
})

puts "App initialize called 3"

promise = Concurrent::Promise.execute do
@sample_app_state.subscribe do |latest_app_state|
# Safe operation, as Async ensures execution is handled in the appropriate thread
@props.on_next({
"todo_text" => latest_app_state.todo_text,
"todo_items" => latest_app_state.todo_items
})
end
end

# Wait for the promise to complete before moving forward
promise.wait

puts "App initialize called 4"
end

def render
puts "App render called"
children = [button("Add todo", Proc.new {puts "suga"}, $button_style)]
# children = [button("Add todo")]

puts "App render called 2"

# props.value["todo_items"].each do |todo_item|
# text = "#{todo_item.text} (#{todo_item.done ? 'done' : 'to do'})."
# children << unformatted_text(text, $text_style)
# children << unformatted_text(text)
# end

puts "App render called 3"

node(children)
end

def dispose
@app_state_subscription.dispose
end
end

class Root < BaseComponent
def initialize
super({})
end

def render
root_node([App.new])
end
end
Loading