;;
;; Copyright 2010 Mikhail Kryshen <mikhail@kryshen.net>
;;
;; This file is part of Indyvon.
;;
;; Indyvon is free software: you can redistribute it and/or modify it
;; under the terms of the GNU Lesser General Public License version 3
;; only, as published by the Free Software Foundation.
;;
;; Indyvon is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with Indyvon.  If not, see
;; <http://www.gnu.org/licenses/>.
;;

(ns net.kryshen.indyvon.demo
  "Indyvon demo and experiments."
  (:gen-class)
  (:use
   (net.kryshen.indyvon core layers component))
  (:import
   (net.kryshen.indyvon.core Size)
   (java.awt Color)
   (javax.swing JFrame)))

(defn draw-button!
  "Draws button immediately (but uses callback for button action
   unlike IMGUI)."
  [id content callback & args]
  (with-handlers id
    (let [shadow-offset 2
          padding 4
          border-width 1
          offset (if (picked? id) (/ shadow-offset 2) 0)
          ^Color color (:alt-back-color *theme*)
          color (if (hovered? id) (.brighter color) color)
          width (- *width* shadow-offset)
          height (- *height* shadow-offset)]
      (with-color Color/BLACK
        (.fillRect *graphics* shadow-offset shadow-offset width height))
      (with-color color
        (.fillRect *graphics* offset offset width height))
      (draw! (border content border-width padding)
             offset offset width height))
    ;; Event handlers
    (:mouse-entered _ (repaint))
    (:mouse-exited _ (repaint))
    (:mouse-pressed _ (repaint))
    (:mouse-released _ (repaint))
    (:mouse-clicked _ (apply callback args))))

(defn combine-colors [^Color color1 ^Color color2 c]
  "Returns color between color1 and color2. When c (0 <= c <= 1.0) is
   closer to 0 the returned сolor is closer to color1."
  (case c
    0.0 color1
    1.0 color2
    (let [rgb1 (.getRGBComponents color1 nil)
          rgb2 (.getRGBComponents color2 nil)
          rgb (float-array (map #(+ (* (- 1 c) %1) (* c %2)) rgb1 rgb2))]
      (Color. (aget rgb 0) (aget rgb 1) (aget rgb 2) (aget rgb 3)))))

(defn animate
  "Changes atom value according to specified range, speed, and current
   frame interval. Invokes repaint if change happens."
  [atom from to speed]
  (let [prev @atom
        state (cond
               (zero? speed) :stop
               (= prev from) (if (pos? speed) :start :stop)               
               (= prev to) (if (neg? speed) :start :stop)
               :default :continue)]
    (if (= state :stop)
       prev
       (let [interval (if (= state :start) 1 *interval*)
             step (* speed interval 1E-9)
             val (swap! atom #(-> % (+ step) (max from) (min to)))]
         (repaint)
         val))))

(defn animated-button
  "Create animated button layer."
  [content callback & args]
  (let [padding 4
        border-width 1
        shadow-offset 2
        face (border content padding border-width)
        highlight (atom 0)
        animation-speed (atom 0)]
    (interval-layer
     (reify
      Layer
      (render! [button]
        (with-handlers button
          (let [hovered (hovered? button)
                offset (if (picked? button) (/ shadow-offset 2) 0)
                color (combine-colors
                       (:alt-back-color *theme*) Color/WHITE
                       (animate highlight 0.0 1.0 @animation-speed))
                width (- *width* shadow-offset)
                height (- *height* shadow-offset)]
            (with-color Color/BLACK
              (.fillRect *graphics*
                         shadow-offset shadow-offset
                         width height))
            (with-color color
              (.fillRect *graphics* offset offset width height))
            (draw! (border content border-width padding)
                   offset offset width height))
          ;; Event handlers
          (:mouse-entered _
            (reset! animation-speed 4)
            (repaint))
          (:mouse-exited _
            (reset! animation-speed -2)
            (repaint))
          (:mouse-pressed _ (repaint))
          (:mouse-released _ (repaint))
          (:mouse-clicked _ (apply callback args))))
     (layer-size [button]
       (let [face-size (layer-size face)]
         (Size. (+ (:width face-size) shadow-offset)
                (+ (:height face-size) shadow-offset))))))))

(def button1 (animated-button (label "Animated button 1")
                              println "Animated button 1 clicked"))

(def button2 (animated-button (label "Animated button 2")
                              println "Animated button 2 clicked"))

(def test-layer1
  (reify
   Layer
   (render! [layer]
     (with-handlers layer
       (with-color (if (hovered? layer) Color/ORANGE Color/RED)
         (.fillRect *graphics* 0 0 *width* *height*))
       (:mouse-entered e
        (repaint)
        (println e))
       (:mouse-exited e
        (repaint)
        (println e))
       (:mouse-moved e
        (println e))))
   (layer-size [layer]
     (Size. 30 20))))

(def test-layer1b (border test-layer1 2 3))

(def test-layer2
  (reify
   Layer
   (render! [layer]
     (doto *graphics*
       (.setColor Color/YELLOW)
       (.fillRect 0 0 *width* *height*))
     (with-rotate 0.5 0 0
       (draw! test-layer1b 30 25))
     (draw! test-layer1 55 5))
   (layer-size [layer]
     (Size. 70 65))))

(def test-layer2m (miniature test-layer2 30 30))

(def test-layer3 (border (label "Sample\ntext" :right :bottom)))

(def root
  (reify
   Layer
   (render! [layer]
     ;;(repaint)
     (doto *graphics*
       (.drawLine 0 0 *width* *height*)
       (.drawLine *width* 0 0 *height*)
       ;; Random color to see when repaint happens.
       (.setColor (rand-nth [Color/BLACK Color/BLUE Color/RED]))
       (.fillOval 5 5 20 20))
     (draw! test-layer2 30 20)
     (draw! test-layer2m 120 50)
     (draw! test-layer3 100 100 80 50)
     (draw! button1 50 160)
     (with-rotate (/ Math/PI 6) 250 200
       (draw! button1 200 160))
     (draw! button2 50 250)
     (with-bounds 100 200 140 30
       (draw-button! :button
        (label "Immediate button" :center :center)
        #(println "Button clicked!"))))
   (layer-size [layer]
     (Size. 400 300))))

;; Main viewport
(def vp (viewport root))

;; Miniature (rendered asynchronously)
(def vp-miniature (border (viewport-miniature vp 100 75)))

;; Main scene
(def scene
  (fps-layer
   (decorate-layer vp [_]
     (draw! vp)
     (draw! vp-miniature (- *width* 105) 5))))

(defn show-frame [layer]
  (doto (make-jframe "Test" layer)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

(defn -main []
  (println "Try to drag the viewport.")
  (show-frame scene))

(comment
  (show-frame (viewport-miniature vp 200 150)))