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

Set text color of selected table row #66

Closed chip closed 1 month ago

chip commented 1 month ago

First off, THANK YOU for this amazing project. It's truly special! 😄

I tried overriding #background_color of your BasicTableColor example and it still returned the default blue on MacOS, so I was curious if there's a workaround?

Thanks again.

AndyObtiva commented 1 month ago

Thanks for the kind words.

I was able to override the background_color with no problem in the code below (has red background with half transparency). One thing to keep in mind is background_color_column must be included inside table as the last column, and each model in the cell_rows data-bound model collection (e.g. self.animals) must have a background_color method that returns the color as rgb hash, rgba hash, rgb array, rgba array, hexadecimal string, hexadecimal number, or color name string.

Screenshot 2024-05-25 at 5 29 11 PM
require 'glimmer-dsl-libui'

class BasicTableColorRedBackground
  Animal = Struct.new(:name, :sound, :mammal)

  class AnimalPresenter < Animal
    def name_color
      color = case name
      when 'cat'
        :red
      when 'dog'
        :yellow
      when 'chicken'
        :beige
      when 'horse'
        :purple
      when 'cow'
        :gray
      end
      [name, color]
    end

    def sound_color
      color = case name
      when 'cat', 'chicken', 'cow'
        :blue
      when 'dog', 'horse'
        {r: 240, g: 32, b: 32}
      end
      [sound, color]
    end

    def mammal_description_color
      color = case name
      when 'cat', 'dog', 'horse', 'cow'
        :green
      when 'chicken'
        :red
      end
      [mammal, 'mammal', color]
    end

    def image_description_color
      color = case name
      when 'cat', 'dog', 'horse'
        :dark_blue
      when 'chicken'
        :beige
      when 'cow'
        :brown
      end
      [img, 'Glimmer', color]
    end

    def img
      # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
      [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
    end

    def background_color
      {r: 255, g: 0, b: 0, a: 0.5}
    end
  end

  include Glimmer

  attr_accessor :animals

  def initialize
    @animals = [
      AnimalPresenter.new('cat', 'meow', true),
      AnimalPresenter.new('dog', 'woof', true),
      AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false),
      AnimalPresenter.new('horse', 'neigh', true),
      AnimalPresenter.new('cow', 'moo', true),
    ]
  end

  def launch
    window('Animals', 500, 200) {
      table {
        text_color_column('Animal')
        text_color_column('Sound')
        checkbox_text_color_column('Description')
        image_text_color_column('GUI')
        background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models

        cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
      }
    }.show
  end
end

BasicTableColorRedBackground.new.launch

The background_color method on the model can return a different value for every model object instance if you want to vary the background color, you can pass it directly to the model as an extra attribute or have it calculated based on other attributes, like an index.

Here is an example where we alternate colors for even and odd rows between red and gree.

Screenshot 2024-05-25 at 5 35 25 PM
require 'glimmer-dsl-libui'

class BasicTableColorAlternatingBackground
  Animal = Struct.new(:name, :sound, :mammal, :index)

  class AnimalPresenter < Animal
    def name_color
      color = case name
      when 'cat'
        :red
      when 'dog'
        :yellow
      when 'chicken'
        :beige
      when 'horse'
        :purple
      when 'cow'
        :gray
      end
      [name, color]
    end

    def sound_color
      color = case name
      when 'cat', 'chicken', 'cow'
        :blue
      when 'dog', 'horse'
        {r: 240, g: 32, b: 32}
      end
      [sound, color]
    end

    def mammal_description_color
      color = case name
      when 'cat', 'dog', 'horse', 'cow'
        :green
      when 'chicken'
        :red
      end
      [mammal, 'mammal', color]
    end

    def image_description_color
      color = case name
      when 'cat', 'dog', 'horse'
        :dark_blue
      when 'chicken'
        :beige
      when 'cow'
        :brown
      end
      [img, 'Glimmer', color]
    end

    def img
      # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
      [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
    end

    def background_color
      if index.even?
        {r: 255, g: 0, b: 0, a: 0.5}
      else
        {r: 0, g: 255, b: 0, a: 0.5}
      end
    end
  end

  include Glimmer

  attr_accessor :animals

  def initialize
    @animals = [
      AnimalPresenter.new('cat', 'meow', true, 0),
      AnimalPresenter.new('dog', 'woof', true, 1),
      AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false, 2),
      AnimalPresenter.new('horse', 'neigh', true, 3),
      AnimalPresenter.new('cow', 'moo', true, 4),
    ]
  end

  def launch
    window('Animals', 500, 200) {
      table {
        text_color_column('Animal')
        text_color_column('Sound')
        checkbox_text_color_column('Description')
        image_text_color_column('GUI')
        background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models

        cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
      }
    }.show
  end
end

BasicTableColorAlternatingBackground.new.launch

Here is another example where the background color is passed to each model as an extra attribute.

Screenshot 2024-05-25 at 5 39 13 PM
require 'glimmer-dsl-libui'

class BasicTableColorCustomBackground
  Animal = Struct.new(:name, :sound, :mammal, :background_color)

  class AnimalPresenter < Animal
    def name_color
      color = case name
      when 'cat'
        :red
      when 'dog'
        :yellow
      when 'chicken'
        :beige
      when 'horse'
        :purple
      when 'cow'
        :gray
      end
      [name, color]
    end

    def sound_color
      color = case name
      when 'cat', 'chicken', 'cow'
        :blue
      when 'dog', 'horse'
        {r: 240, g: 32, b: 32}
      end
      [sound, color]
    end

    def mammal_description_color
      color = case name
      when 'cat', 'dog', 'horse', 'cow'
        :green
      when 'chicken'
        :red
      end
      [mammal, 'mammal', color]
    end

    def image_description_color
      color = case name
      when 'cat', 'dog', 'horse'
        :dark_blue
      when 'chicken'
        :beige
      when 'cow'
        :brown
      end
      [img, 'Glimmer', color]
    end

    def img
      # scale image to 24x24 (can be passed as file path String only instead of Array to avoid scaling)
      [File.expand_path('../icons/glimmer.png', __dir__), 24, 24]
    end
  end

  include Glimmer

  attr_accessor :animals

  def initialize
    @animals = [
      AnimalPresenter.new('cat', 'meow', true, {r: 134, g: 83, b: 24}),
      AnimalPresenter.new('dog', 'woof', true, {r: 134, g: 183, b: 224}),
      AnimalPresenter.new('chicken', 'cock-a-doodle-doo', false, {r: 34, g: 183, b: 24}),
      AnimalPresenter.new('horse', 'neigh', true, {r: 34, g: 183, b: 124}),
      AnimalPresenter.new('cow', 'moo', true, {r: 34, g: 83, b: 24}),
    ]
  end

  def launch
    window('Animals', 500, 200) {
      table {
        text_color_column('Animal')
        text_color_column('Sound')
        checkbox_text_color_column('Description')
        image_text_color_column('GUI')
        background_color_column # must always be the last column and always expects data-binding model attribute `background_color` when binding to Array of models

        cell_rows <= [self, :animals, column_attributes: {'Animal' => :name_color, 'Sound' => :sound_color, 'Description' => :mammal_description_color, 'GUI' => :image_description_color}]
      }
    }.show
  end
end

BasicTableColorCustomBackground.new.launch

I hope that helps.

If you need further help, please share your code so that I am better able to help you.

You can close the issue if you think it is resolved. Otherwise, feel free to ask further questions.

chip commented 1 month ago

Thanks so much for your prompt and thorough reply. The examples you provided there and from running glimmer examples has been incredibly helpful! 👍🏻

Yes, under normal circumstances I'm able to override the background_color, but not when the row is "selected" (or clicked). My original code was disorganized, so instead I ran your example code above to show a before and after.

This shows when table initially renders. All is well here:

Screenshot 2024-05-26 at 11 04 46 AM

And here the background is blue when I click on the last row containing "cow", "moo", "mammal" (sorry, my cursor is hidden in this screenshot):

Screenshot 2024-05-26 at 11 05 40 AM

It looks like the native widget behavior takes over from there upon selection. I tried a number of approaches using on_row_clicked and on_selection_changed to determine the row index and from there attempt to override the text color or background_color, all to no avail.

Any pointers are greatly appreciated. I'll be happy to close if this isn't possible. Thanks again for your help!

AndyObtiva commented 1 month ago

Yeah, that is the Selection Background Color, which is different. It comes from the Operating System Theme, and it cannot be overridden by design given LibUI is a Native GUI Toolkit that aims to conform to the Operating System Theme and Look & Feel to build Graphical User Interfaces that are more familiar, intuitive, and user-friendly to users who already know how the operating system apps look and feel.

That said, LibUI allows you to build your own non-native table custom control from scratch on top of area. Here is an example of a button custom control and label custom control built from scratch on top of area: https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md#area-based-custom-controls Of course, that means, the code would have to manage all table features, like selection management, and generation of table rows based on model data. Here is another example of doing something like this to render graphs and charts from scratch, and provide them as new custom controls that could be reused in multiple apps: https://github.com/AndyObtiva/glimmer-libui-cc-graphs_and_charts If a table custom control was built to facilitate customizing row background selection color, it would only require effort the first time it is built, but afterwards, it could be used as a custom control component.

Alternatively, you can use a different toolkit like Glimmer DSL for SWT, which provides a way to override the OS selection background color according to this StackOverflow post: https://stackoverflow.com/questions/64502873/how-to-change-swt-table-item-selection-background-color#:~:text=The%20selection%20colour%20is%20normally,yourself%20in%20the%20erase%20method.

Also, you could try to use non-native widget toolkits like Glimmer DSL for Tk, Glimmer DSL for FX, Glimmer DSL for Swing, and Glimmer DSL for JavaFX if you would like to have your own table selection color.

I've never personally had this requirement, as business projects typically like to conform to the native look and feel of the OS. I worked at a company once that hated that the Swing GUI Toolkit made developers customize the look and feel everywhere instead of simply conforming to the OS standards, so they used SWT instead, another native GUI Toolkit (like LibUI), and that made their business apps a lot more user friendly and simpler to build for their users. Going against the OS look and feel is generally not recomended in desktop development, and is not very common. It's a huge benefit in desktop development not to have to customize the look and feel completely, yet only in very rare circumstances (which is why LibUI supports building custom controls on top of area), as conforming to OS standards enables much higher productivity in building desktop apps over web apps, which waste a lot of time on look and feel. If someone is trying to build desktop apps as web apps, they're missing the whole point that enables the much higher productivity in desktop development.

I hope I answered your question sufficiently.

chip commented 1 month ago

Great explanation. Yeah, I agree it makes good sense to accept the native behavior and work within those constraints. I may have been trying to over-optimize a bit here. 😃

What you've provided with this toolkit is greatly appreciated. You've been quite generous with your time and examples. Thank you again for all of your help with my request!

AndyObtiva commented 1 month ago

You're welcome