# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.
# 
# Some parts are
# Copyright 2004-2007, wxRuby development team
# released under the MIT-like wxRuby2 license

# Adapted for wxRuby3
###

require 'wx'

# This sample demonstrates how to dynamically connect and disconnect
# event handlers, and how to create custom event types and listeners
# associated with user-defined control classes.

module Event

  # A custom type of event associated with a target control. Note that for
  # user-defined controls, the associated event should inherit from
  # Wx::CommandEvent rather than Wx::Event.
  class TargetHitEvent < Wx::CommandEvent
    # Create a new unique constant identifier, associate this class
    # with events of that identifier, and create a shortcut 'evt_target'
    # method for setting up this handler.
    EVT_HIT_TARGET = Wx::EvtHandler.register_class(self, nil, 'evt_target', 1)

    def initialize(target, score, distance)
      # The constant id is the arg to super
      super(EVT_HIT_TARGET)
      # client_data should be used to store any information associated
      # with the event.
      self.client_data = { :score => score, :distance => distance  }
      self.id = target.get_id
    end

    # Returns the points score associated with this event
    def score
      client_data[:score]
    end

    # Returns the distance (in pixels) from the centre of the target
    def distance
      client_data[:distance]
    end
  end

  class CountEvent < Wx::CommandEvent
    EVT_COUNTER = Wx::EvtHandler.register_class(self, nil, 'evt_counter', 1)

    def initialize(id)
      super(EVT_COUNTER, id)
    end
  end

  # An example of a simple user-written control, which displays a
  # "bulls-eye" like target, and sends events with a score and distance
  class TargetControl < Wx::Window
    TargetCircle = Struct.new(:radius, :score, :brush)

    def initialize(parent, *args)
      super(parent, *args)

      # Set up the scores and sizes of the rings
      @radii = [
        TargetCircle[ 0.1, 20, Wx::RED_BRUSH ],
        TargetCircle[ 0.25, 10, Wx::BLUE_BRUSH ],
        TargetCircle[ 0.4, 5, Wx::GREEN_BRUSH ] ]
      evt_paint { | e | on_paint(e) }
      evt_left_down { | e | on_left_down(e) }
    end

    # What point is at the centre (assuming this control is always square)
    def centre_point
      size.width / 2
    end

    # Called whenever the target is repainted, draws a series of
    # concentric circles
    def on_paint(evt)
      paint do | dc |
        dc.clear
        @radii.reverse_each do | circ |
          dc.brush = circ.brush
          dc.draw_circle(centre_point, centre_point,
                         ( size.width * circ.radius).to_i )
        end
      end
    end

    # Test if the target was hit, and generate a TargetHitEvent if so
    def on_left_down(evt)
      # quick bit of pythagoras...
      distance = Math.sqrt( ( evt.x - centre_point ) ** 2  +
                            ( evt.y - centre_point ) ** 2 )
      # See which target ring, if any, was hit by the event
      @radii.each do | circ |
        if distance < ( size.width * circ.radius)
          # Create an instance of the event
          evt = TargetHitEvent.new(self, circ.score, distance)
          # This sends the event for processing by listeners
          event_handler.process_event(evt)
          break
        end
      end
    end

    def try_before(event)
      event_handler.queue_event(CountEvent.new(self.id)) if TargetHitEvent === event
      super
    end
  end

  # Container frame for the target control
  class TargetFrame < Wx::Frame
    def initialize(title)
      super(nil, :title => title, :size => [300, 300])

      self.icon = Wx.Icon(:sample, Wx::BITMAP_TYPE_XPM, art_path: File.join(__dir__, '..'))

      @tgt = TargetControl.new(self)
      # This user-defined event handling method was set up by
      # EvtHandler.register_class, above
      evt_target(@tgt.get_id) { | e | on_target(e) }
      @counter = 0
      evt_counter(@tgt.get_id) { | e | on_counter(e) }
      @listening = true
      evt_size { | e | on_size(e) }
      setup_menus
      create_status_bar(2)
    end

    # What's done when the target is hit
    def on_target(evt)
      msg = "Target hit for score %i, %.2f pixels from centre" %
            [ evt.score, evt.distance ]
      set_status_text(msg, 0)
    end

    def on_counter(evt)
      @counter += 1
      set_status_text(@counter.to_s, 1)
    end

    # Keep the target centred and square
    def on_size(evt)
      smaller = [ evt.size.width, evt.size.height ].min
      @tgt.centre
      @tgt.size = Wx::Size.new(smaller, smaller)
      @tgt.refresh
    end

    # Toggle whether or not we are listening for events from the bulls-eye
    # target
    def on_toggle_connect
      if @listening
        # Remove :evt_target event handler for the @tgt
        disconnect(@tgt.get_id, Wx::ID_ANY, :evt_target)
        menu_bar.check(TOGGLE_LISTEN, false)
        self.status_text = "Ignoring target events"
        @listening = false
      else
        # Restore evt_target event handler for the @tgt
        evt_target(@tgt.get_id) { | e | on_target(e) }
        menu_bar.check(TOGGLE_LISTEN, true)
        self.status_text = ''
        @listening = true
      end
    end

    def on_about
      msg =  sprintf("This is the About dialog of the event handling sample.\n" \
                     "Welcome to wxRuby, version %s", Wx::WXRUBY_VERSION)

      Wx::MessageDialog(self, msg, 'About Event Handling',
                        Wx::OK | Wx::ICON_INFORMATION) do |about_dlg|
        about_dlg.ok_label = Wx::ButtonLabel.new('Close')
        about_dlg.show_modal
      end
    end

    TOGGLE_LISTEN = 1001
    def setup_menus
      menu_file = Wx::Menu.new
      menu_help = Wx::Menu.new
      menu_help.append(Wx::ID_ABOUT, "&About...\tF1", "Show about dialog")
      evt_menu(Wx::ID_ABOUT) { on_about }
      menu_file.append(Wx::ID_EXIT, "E&xit\tAlt-X", "Quit this program")
      evt_menu(Wx::ID_EXIT) { self.close }
      menu_file.append_check_item(TOGGLE_LISTEN, "L&isten for events",
                                   "Toggle listening for target events")
      evt_menu(TOGGLE_LISTEN) { on_toggle_connect }

      menu_bar = Wx::MenuBar.new
      menu_bar.append(menu_file, "&File")
      menu_bar.append(menu_help, "&Help")

      self.menu_bar = menu_bar
      menu_bar.check(TOGGLE_LISTEN, true)
    end
  end

end

module EventSample

  include WxRuby::Sample if defined? WxRuby::Sample

  def self.describe
    { file: __FILE__,
      summary: 'wxRuby event handling example.',
      description: <<~__TXT
        wxRuby example demonstrating event handling.
        This sample demonstrates how to dynamically connect and disconnect
        event handlers, and how to create custom event types and listeners
        associated with user-defined control classes.
        __TXT
     }
  end

  def self.activate
    frame = Event::TargetFrame.new("Event Handling Sample")
    frame.show
    frame
  end

  if $0 == __FILE__
    Wx::App.run { EventSample.activate }
  end

end
