Closed phuongnd08 closed 1 year ago
That depends on whether you're using stable paths by nesting a circle
directly under an area
or by using redrawable dynamic paths by using an on_draw
listener under area
and then nesting a circle
within it (you'd have to share the full code of your file to make it clear to me which approach you follow unless the solution below works).
In the case of a stable path (the simpler approach), you don't have to do anything other than change the fill
color of the circle
object.
But, in the case of a dynamic path (the more complicated approach, albeit slightly more performant), you'd have to call the area.queue_redraw_all
method after every change you make to paths (shapes) nested underneath the area
's on_draw
listener:
area_object.queue_redraw_all
This should address your concern fully, so I am closing this issue. But, you can still reply to the issue if you want to confirm if the solution works or ask further questions (or even re-open the issue if needed).
Cheers.
This is how I do it:
controller = GameController.new
controller.run_game_in_thread
window('hello world'){
fullscreen true
scrolling_area {
text {
indicator = string
controller.on_stat_updated do |controller|
indicator.string = controller.indication
end
}
color_indicator = circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
stroke r: 0, g: 0, b: 0, thickness: 2
fill controller.color
}
controller.on_stat_updated do |controller|
puts "fill with color = #{controller.color}"
color_indicator.fill = controller.color
end
controller is a model object, with the ability to subscribe to some of it changes
class GameController
...
def on_stat_updated(&block)
@callbacks << block
notify_callbacks
end
def notify_callbacks
@callbacks.each { |callback| callback.call(self) }
end
end
Tried this, still no color update
game_area = scrolling_area {
text {
indicator = string
controller.on_stat_updated do |controller|
indicator.string = controller.indication
end
}
color_indicator = circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
stroke r: 0, g: 0, b: 0, thickness: 2
fill controller.color
}
controller.on_stat_updated do |controller|
puts "fill with color = #{controller.color}"
color_indicator.fill = controller.color
game_area&.queue_redraw_all
end
Tried this too, no color update:
color_indicator = nil
color_area = area {
color_indicator = circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
stroke r: 0, g: 0, b: 0, thickness: 2
fill <= controller.color
}
}
controller.on_stat_updated do |controller|
puts "fill with color = #{controller.color}"
color_indicator.fill = controller.color
color_area&.queue_redraw_all
end
Whole file incase you want to quickly debug:
#!/usr/bin/env ruby
require 'glimmer-dsl-libui'
require 'byebug'
require 'observer'
require 'shellwords'
require 'timeout'
require 'mpv'
include Glimmer
class AudioPlayer
def initialize(path)
@path = path
end
def play
session.command "loadfile", @path
end
def mpv_args
[
"--profile=low-latency",
]
end
def session
@session ||= MPV::Session.new(user_args: mpv_args).tap do |s|# contains both a MPV::Server and a MPV::Client
s.callbacks << method(:event_happened)
end
end
def event_happened(event)
case event["event"]
when "pause"
@paused = true
when "unpause"
@paused = false
end
end
end
class GameController
attr_accessor :total_counter, :correct_counter, :game_session_started
attr_accessor :current_char
attr_reader :color
def initialize
@total_counter = 0
@correct_counter = 0
@game_session_started = false
@callbacks = []
@color = :black
end
def play_word(word)
@players ||= {}
@players[word] ||=
begin
path = File.expand_path("wsound/#{word}.aiff", __dir__)
unless File.exists?(path)
system "say #{Shellwords.escape(word)} -o #{Shellwords.escape(path)}"
end
AudioPlayer.new(path)
end
@players[word].play
self.color = word.downcase.to_sym
end
def color=(c)
@color = c
notify_callbacks
end
def on_stat_updated(&block)
@callbacks << block
notify_callbacks
end
def notify_callbacks
@callbacks.each { |callback| callback.call(self) }
end
def check_answer(char)
puts "user_answer=#{char}"
@user_answer = char
@user_answered_at = Time.now
end
def clear_answer
@user_answer = nil
@user_answered_at = nil
end
def toggle_game_mode!
@game_session_started = !@game_session_started
end
def run_game_in_thread
Thread.new { run_game }
end
GAME_KEYS = {
"a" => "red",
"b" => "green",
"c" => "yellow"
}
def duration_per_game
3
end
def run_game
loop do
unless @game_session_started
sleep 0.1
next
end
puts "new game"
self.current_char = GAME_KEYS.keys.sample
self.total_counter += 1
announce_target(GAME_KEYS[current_char])
clear_answer
@game_started_at = Time.now
outcome = nil
perform_for(duration: duration_per_game, interval: 0.05) do
unless @user_answer.nil?
answwer_time = @user_answered_at - @game_started_at
puts "answer-time=#{(answwer_time * 1000).floor(1)} ms"
if answwer_time > duration_per_game
outcome = :timeout
elsif @user_answer == self.current_char
outcome = :right
else
outcome = :wrong
end
next true
end
end
outcome ||= :timeout
if outcome == :right
self.correct_counter += 1
notify_callbacks
end
announce_outcome(outcome)
end
rescue => e
p e
end
def perform_for(duration:, interval: 0.05)
started_at = Time.now
while Time.now - started_at < duration
sleep interval
break if yield
end
end
def announce_outcome(outcome)
play_sound(outcome.to_s)
end
def announce_target(word)
say("ready")
sleep 0.2
say("3")
sleep 0.2
say("2")
sleep 0.2
say("1")
play_word(word)
end
def say(msg, voice: nil)
Timeout.timeout(3) do
cmd = "say #{Shellwords.escape(msg)}"
cmd += " -v #{Shellwords.escape(voice)}" if voice
system cmd
puts "said: #{msg}"
end
rescue Timeout::Error => e
puts "Timeout error while speaking #{msg} using voice=#{voice}"
end
def play_sound(sound)
path = File.expand_path("sound/#{sound}.mp3", __dir__)
system "afplay #{Shellwords.escape(path)}"
end
def indication
if @game_session_started
"#{correct_counter}/#{total_counter}"
else
"Press ENTER to start/stop game"
end
end
end
controller = GameController.new
controller.run_game_in_thread
window('hello world'){
fullscreen true
scrolling_area {
text {
indicator = string
controller.on_stat_updated do |controller|
indicator.string = controller.indication
end
}
color_indicator = nil
color_area = area {
color_indicator = circle(200, 200, 90) { # declarative stable path (implicit path syntax for a single shape nested directly under area)
stroke r: 0, g: 0, b: 0, thickness: 2
fill <= controller.color
}
}
controller.on_stat_updated do |controller|
puts "fill with color = #{controller.color}"
color_indicator.fill = controller.color
color_area&.queue_redraw_all
end
on_mouse_down do |mouse_event|
if mouse_event[:down] == 1
controller.check_answer("c")
elsif mouse_event[:down] == 3
controller.check_answer("d")
end
end
on_key_down do |key_event|
# if key_event[:ext_key] == :escape
# LibUI.quit
# else
case key_event[:key]
when "a", "b" then controller.check_answer(key_event[:key])
when "\n" then controller.toggle_game_mode!
end
# end
nil
end
}
}.show
And the Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem 'glimmer-dsl-libui'
gem 'byebug'
gem 'dotenv'
gem 'mpv'
@AndyObtiva tagging you just to be sure
I replied in the follow-up issue you created: https://github.com/AndyObtiva/glimmer-dsl-libui/issues/50
Sorry, it seems only I have the right to re-open an issue. I would have re-opened it had you not created the other issue. No worries, let's just continue communication over there.
Using this:
when controller change the color to :green, :red, :yellow, I noticed that the color of the circle is not updated. What am I supposed to do so that the color changes?