AndyObtiva / glimmer-dsl-libui

Glimmer DSL for LibUI - Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library - The Quickest Way From Zero To GUI - If You Liked Shoes, You'll Love Glimmer! - No need to pre-install any prerequisites. Just install the gem and have platform-independent GUI that just works on Mac, Windows, and Linux.
MIT License
458 stars 15 forks source link

[reopen] Cannot update fill of a circle #50

Closed phuongnd08 closed 1 year ago

phuongnd08 commented 1 year ago

I couldn't re-open an issue that I think unsolved, so link it here for attention: https://github.com/AndyObtiva/glimmer-dsl-libui/issues/49

AndyObtiva commented 1 year ago

You are using scrolling_area instead of area, but the same solution applies.

Here is a working code sample with stable paths:

require 'glimmer-dsl-libui'

include Glimmer

window('Circle Random Coloring by Clicking', 400, 400) {
  scrolling_area {
    circle(200, 200, 90) { |c| # declarative stable path (implicit path syntax for a single shape nested directly under area)
      fill r: 202, g: 102, b: 204, a: 0.5
      stroke r: 0, g: 0, b: 0, thickness: 2

      on_mouse_up do
        c.fill = [rand(255), rand(255), rand(255)]
      end
    }
  }
}.show

glimmer-dsl-libui-circle_random_coloring_by_clicking

Here is the example re-written with dynamic paths:

require 'glimmer-dsl-libui'

include Glimmer

window('Circle Random Coloring by Clicking', 400, 400) {
  @area = scrolling_area {
    on_draw do |area_draw_params|
      @circle = circle(200, 200, 90) { |c| # declarative stable path (implicit path syntax for a single shape nested directly under area)
        fill r: rand(255), g: rand(255), b: rand(255), a: 0.5
        stroke r: 0, g: 0, b: 0, thickness: 2
      }
    end

    on_mouse_up do |mouse_event|
      @area.queue_redraw_all if @circle.contain?(mouse_event[:x], mouse_event[:y])
    end
  }
}.show

https://github.com/AndyObtiva/glimmer-dsl-libui/assets/23052/28afcf86-f972-427a-9f46-6ee439bf11fd

Please let me know if either example's code helps you resolve your issue.

AndyObtiva commented 1 year ago

OK, I think I found the culprit in your code from https://github.com/AndyObtiva/glimmer-dsl-libui/issues/49:

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
      }
    }

You are nesting an area { within a scrolling_area {! That's wrong! You must choose to either use an area or a scrolling_area. There is no need to nest an area inside scrolling_area, so you can nest the circle directly under scrolling_area, and that should resolve your issue.

Please confirm if the issue goes away once you apply this solution so that I'd close the issue.

phuongnd08 commented 1 year ago

okay, so I can see that if I use mouse_up to set the color, it's effective. if I call code from a thread, it's not effective.

Here is the code:

    circle(200, 200, 90) do |color_indicator| # 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

      on_mouse_up do
        color_indicator.fill = [rand(255), rand(255), rand(255)]
      end

      Thread.new {
        loop do
          puts "fill with random color"
          color_indicator.fill = [rand(255), rand(255), rand(255)]
          sleep 1
        end
      }
  end

if things run correctly, I am supposed to see a new color whenever I click on the circle, or every seconds. But I only see new color when I click on the circle. There are log in the terminal confirming the thread is running:

fill with random color
fill with random color
fill with random color
fill with random color
fill with random color
fill with random color
fill with random color
fill with random color
phuongnd08 commented 1 year ago

Adding queue_redraw_all doesn't help:

    circle(200, 200, 90) do |color_indicator| # 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

      on_mouse_up do
        color_indicator.fill = [rand(255), rand(255), rand(255)]
      end

      Thread.new {
        loop do
          puts "fill with random color"
          color_indicator.fill = [rand(255), rand(255), rand(255)]
          @area&.queue_redraw_all
          sleep 1
        end
      }
  end
AndyObtiva commented 1 year ago

Yes, that's because if you're updating from a different Thread, you have to wrap all your calls to the GUI with Glimmer::LibUI.queue_main { ... } in order to access the main GUI thread (documented under LibUI Operations and used in Glimmer Meta-Example). Also, you don't need area.queue_redraw_all if you are using stable paths (e.g. nesting circle directly under area or scrolling_area).

This updated sample updates the circle fill color from a Thread (it also demonstrates the new recommended way of building Glimmer DSL for LibUI applications by taking advantage of the Glimmer::LibUI::Application mixin (albeit optional), which provides a .launch method and before_body/after_body hooks that execute before and after building the GUI body):

require 'glimmer-dsl-libui'

include Glimmer

class CircleRandomColoringByThread
  include Glimmer::LibUI::Application

  after_body do
    Thread.new {
      loop do
        puts "fill with random color"
        Glimmer::LibUI.queue_main do
          @circle.fill = [rand(255), rand(255), rand(255)]
        end
        sleep 1
      end
    }
  end

  body {
    window('Circle Random Coloring by Clicking', 400, 400) {
      scrolling_area {
        @circle = circle(200, 200, 90) { |c| # declarative stable path (implicit path syntax for a single shape nested directly under area)
          fill r: 202, g: 102, b: 204, a: 0.5
          stroke r: 0, g: 0, b: 0, thickness: 2

          on_mouse_up do
            c.fill = [rand(255), rand(255), rand(255)]
          end
        }
      }
    }
  }
end

CircleRandomColoringByThread.launch

glimmer-dsl-libui-circle_random_coloring_by_thread

If you are updating indirectly through a model/controller, you still have to wrap the update call to the model/controller (e.g. color_indicator.fill = [rand(255), rand(255), rand(255)]) with Glimmer::LibUI.queue_main { ... }:

      Thread.new {
        loop do
          puts "fill with random color"
          Glimmer::LibUI.queue_main do
            color_indicator.fill = [rand(255), rand(255), rand(255)]
          end
          sleep 1
        end
      }

Please confirm if this resolves your issue in order to close it.

Cheers.

phuongnd08 commented 1 year ago

Thanks, my bad for not reading through the documentation. Though, my thought is either recommend every UI update to go through something link update_ui block (i.e update_ui { code }), and update_ui is an alias of queue_main under the hook, or design the library so that every GUI element attribute update automatically use queue_main as a convention so developer don't have to worry about this in their code. If this has a performance penalty, allow the developer to config this to not be a default behavior to speed up the app if they intend to do something seriously with it later?

AndyObtiva commented 1 year ago

Yeah, I actually automated this requirement completely in Glimmer DSL for SWT, which has an equivalent to queue_main called async_exec. After a certain version release, developers don’t need to use async_exec explicitly in Glimmer DSL for SWT anymore when running from a different thread. It happens automatically for them.

I will consider similarly automating the use of queue_main from other threads in Glimmwr DSL for LibUI. The library is relatively new, so I didn’t get a chance to do it. I’m adding this task to the TODO list.

Otherwise, I am closing this issue. If you have further comments or questions, feel free to reply. I will see your comments even if the issue is closed.