Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions .idea/xframes-ruby.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added .ruby-version
Binary file not shown.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@

#### 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:

Expand All @@ -20,6 +27,7 @@ I recommend to install Ruby via [Scoop](https://scoop.sh/), then
- `sudo apt install ruby-full`
- `sudo gem install ffi`
- `sudo gem install eventmachine`
- `sudo gem install rx`

### Run the application

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

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

96 changes: 5 additions & 91 deletions main.rb
Original file line number Diff line number Diff line change
@@ -1,63 +1,11 @@
require 'ffi'
require 'json'
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 @@ -181,40 +129,6 @@ def to_json(*options)
end


module XFrames
extend FFI::Library
if RUBY_PLATFORM =~ /win32|mingw|cygwin/
ffi_lib './xframesshared.dll'
else
ffi_lib './libxframesshared.so'
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

attach_function :init, [
:string, # assetsBasePath
:string, # rawFontDefinitions
:string, # rawStyleOverrideDefinitions
:OnInitCb,
:OnTextChangedCb,
:OnComboChangedCb,
:OnNumericValueChangedCb,
:OnBooleanValueChangedCb,
:OnMultipleNumericValuesChangedCb,
:OnClickCb
], :void

attach_function :setElement, [:string], :void

attach_function :setChildren, [:int, :string], :void
end

on_init = FFI::Function.new(:void, []) do
puts "OnInit called!"
Expand Down
95 changes: 95 additions & 0 deletions sampleapp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require 'rx'
require_relative 'theme'
require_relative 'widgetnode'

Comment on lines +1 to +6
Copy link
Copy Markdown

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("", []))

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
Comment on lines +27 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve async handling and state management.

The current implementation has several issues:

  1. Direct access to global state
  2. Blocking UI with promise.wait
  3. Hardcoded "New Todo" text

Consider this improved implementation:

-def on_click
-  promise = Concurrent::Promise.execute do
-    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
-
-  promise.wait
-end

+def on_click(text = nil)
+  Concurrent::Promise.execute do
+    service = TodoService.instance
+    current_state = service.state.value
+    new_todo_item = TodoItem.new(text || current_state.todo_text || "New Todo", false)
+    new_state = AppState.new(
+      "",  # Reset input after adding
+      current_state.todo_items + [new_todo_item]
+    )
+    service.state.on_next(new_state)
+  end
+end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def on_click
promise = Concurrent::Promise.execute do
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
promise.wait
end
def on_click(text = nil)
Concurrent::Promise.execute do
service = TodoService.instance
current_state = service.state.value
new_todo_item = TodoItem.new(text || current_state.todo_text || "New Todo", false)
new_state = AppState.new(
"", # Reset input after adding
current_state.todo_items + [new_todo_item]
)
service.state.on_next(new_state)
end
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({})

@app_state_subscription = sample_app_state.subscribe do |latest_app_state|
props.on_next({
"todo_text" => latest_app_state.todo_text,
"todo_items" => latest_app_state.todo_items
})
end
end

def render
children = [button("Add todo", method(:on_click), button_style)]

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

node(children)
end

def dispose
@app_state_subscription.dispose
end
end
Comment on lines +62 to +105
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix state management and async rendering issues.

Several issues need attention:

  1. Direct access to global state
  2. Blocking promises in render method
  3. Subscription variable @app_state_subscription is never set

Apply these improvements:

 class App < BaseComponent
   def initialize
     super({})
 
-    promise = Concurrent::Promise.execute do
-      $sample_app_state.subscribe do |latest_app_state|
-        puts "app state changed"
+    service = TodoService.instance
+    @app_state_subscription = service.state.subscribe do |latest_app_state|
+      puts "app state changed"
 
-        @props.on_next({
-          "todo_text" => latest_app_state.todo_text,
-          "todo_items" => latest_app_state.todo_items
-        })
-      end
+      @props.on_next({
+        "todo_text" => latest_app_state.todo_text,
+        "todo_items" => latest_app_state.todo_items
+      })
     end
 
-    promise.wait
-
     @props.on_next({
       "todo_text" => "",
       "todo_items" => [TodoItem.new("New Todo", false)]
     })
   end
 
   def render
-    children = [button("Add todo", Proc.new {
-      on_click()
-    }, $button_style)]
+    children = [button("Add todo", -> { on_click() }, Theme::Styles.button)]
 
-    promise = Concurrent::Promise.execute do
-      @props.value["todo_items"].each do |todo_item|
-        text = "#{todo_item.text} (#{todo_item.done ? 'done' : 'to do'})."
-        children << unformatted_text(text, $text_style)
-      end
+    @props.value["todo_items"].each do |todo_item|
+      text = "#{todo_item.text} (#{todo_item.done ? 'done' : 'to do'})."
+      children << unformatted_text(text, Theme::Styles.text)
     end
 
-    promise.wait
-
     node(children)
   end
 
   def dispose
     @app_state_subscription.dispose
   end
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class App < BaseComponent
def initialize
super({})
promise = Concurrent::Promise.execute do
$sample_app_state.subscribe do |latest_app_state|
puts "app state changed"
@props.on_next({
"todo_text" => latest_app_state.todo_text,
"todo_items" => latest_app_state.todo_items
})
end
end
promise.wait
@props.on_next({
"todo_text" => "",
"todo_items" => [TodoItem.new("New Todo", false)]
})
end
def render
children = [button("Add todo", Proc.new {
on_click()
}, $button_style)]
promise = Concurrent::Promise.execute do
@props.value["todo_items"].each do |todo_item|
text = "#{todo_item.text} (#{todo_item.done ? 'done' : 'to do'})."
children << unformatted_text(text, $text_style)
end
end
promise.wait
node(children)
end
def dispose
@app_state_subscription.dispose
end
end
class App < BaseComponent
def initialize
super({})
service = TodoService.instance
@app_state_subscription = service.state.subscribe do |latest_app_state|
puts "app state changed"
@props.on_next({
"todo_text" => latest_app_state.todo_text,
"todo_items" => latest_app_state.todo_items
})
end
@props.on_next({
"todo_text" => "",
"todo_items" => [TodoItem.new("New Todo", false)]
})
end
def render
children = [button("Add todo", -> { on_click() }, Theme::Styles.button)]
@props.value["todo_items"].each do |todo_item|
text = "#{todo_item.text} (#{todo_item.done ? 'done' : 'to do'})."
children << unformatted_text(text, Theme::Styles.text)
end
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