[PYTHON] Try GUI programming with Hy

This article is the 16th day article of Lisp Advent Calendar 2019.

Summary of this article

As an introduction to GUI programming in Hy, generative art using the GUI library tkinter included in the Python standard library. To implement.

It is a specification that you can change the parameters by key input in drawing a figure using a trigonometric function like this. 2019-12-15_23h18_53.png

Target audience

Environment

The author's environment is as follows.

Building a virtual environment and installing modules

The GUI library tkinter is a Python standard library, so Hy is the only third-party module to install in your virtual environment. Hy has installed the latest version as of 12/10 (https://github.com/hylang/hy/tree/39c150d8299d9ff8f47bb78415e22a9fe976d7e5).

cd %USERPROFILE$\.virtualenvs
py -3 -m venv venv_hy_geometric_pattern
cd venv_hy_geometric_pattern
Scripts\activate.bat
pip install --upgrade pip
pip install git+https://github.com/hylang/hy.git
deactivate

specification

tk.Tk () Create a Canvas in the window created as an instance and draw a shape in it. Redraw the Canvas at approximately 60 fps for animation. The coordinates of a point at a certain time are calculated by the following formula. Change $ \ theta $ by $ \ mathrm {d} \ theta $ to create an array of points at that time.

\left\{ \begin{array}{}
x(\theta , t) = x_{\mathrm{center}} + r \cos \theta + a \cos (\phi \left( t \right) + c \theta) \\

y(\theta , t) = y_{\mathrm{center}} + r \sin \theta + a \sin (\phi \left( t \right) + c \theta)
\end{array} \right.
0 \leq \theta < 2 \pi \\
0 \leq \phi < 2 \pi \\
0 \leq c < 2 \pi \\
0 < r + a < \min(x_{\mathrm{center}}, y_{\mathrm{center}}) \\

$ \ left (x_ {\ mathrm {center}}, y_ {\ mathrm {center}} \ right) $ is the center coordinate of the Canvas. $ \ phi $ causes $ 0 \ leq \ phi <2 \ pi $ to loop over time.

Allows you to change the step size $ \ mathrm {d} \ theta $ of $ r, a, c $ and $ \ theta $ above by key input. The key assignments are shown in the table below. It also sets the q key to close and exit the window.

variable Decrease key Increase key
r z a
a x s
c c d
\mathrm{d} \theta v f

Package configuration

hySuppose the directory has been added to pythonpath. The contents of \ _ \ _ init \ _ \ _. Hy are empty, and we will implement the function in gui.hy. Call and execute geometric_pattern.gui from geometric_pattern.bat.

hy ─┬─ geometric_pattern ─┬─ __init__.hy
    │                     │
    │                     └─ gui.hy
    │
    └─ geometric_pattern.bat

Implementation

gui.hy


(import [math [sin cos radians]])
(import sys)
(import [tkinter :as tk])

;Canvas size
(setv *width* 800)
(setv *height* 600)


(defclass GeometricPattern []
  (setv *d-theta-max* 20)
  (setv *deg* 360)
  (setv *d-phi* 1)

  (defn --init-- [self &optional [center-x 0] [center-y 0]]
    "Initialize the parameters and get a list of points"
    (setv (. self center-x) center-x)
    (setv (. self center-y) center-y)

    (setv (. self r)
          (int (-> 0.8
                   (* (min (. self center-x) (. self center-y)))
                   (/ 3))))
    (setv (. self a)
          (int (-> 0.8
                   (* 2 (min (. self center-x) (. self center-y)))
                   (/ 3))))
    (setv (. self phi) 0)
    (setv (. self c) 104)

    ;;Step width when changing theta
    (setv (. self d-theta) 1)

    ((. self fetch-points)))
  
  (defn dec-r [self event]
    "Decrease r"
    (setv (. self r) (dec (. self r)))
    (if (< (. self r) 1)
        (setv (. self r) 1)))

  (defn inc-r [self event]
    "Increase r. The upper limit is specified so that it does not extend beyond the short side of Canvas."
    (if (< (+ (. self r) (. self a))
           (min (. self center-x) (. self center-y)))
        (setv (. self r) (inc (. self r)))))
  
  (defn dec-a [self event]
    "Decrease a"
    (setv (. self a) (dec (. self a)))
    (if (< (. self a) 1)
        (setv (. self a) 1)))
  
  (defn inc-a [self event]
    "Increase a. The upper limit is specified so that it does not extend beyond the short side of Canvas."
    (if (< (+ (. self r) (. self a))
           (min (. self center-x) (. self center-y)))
        (setv (. self a) (inc (. self a)))))
  
  (defn dec-d-theta [self event]
    (setv (. self d-theta) (dec (. self d-theta)))
    (if (< (. self d-theta) 1)
        (setv (. self d-theta) 1)))
  
  (defn inc-d-theta [self event]
    (setv (. self d-theta) (inc (. self d-theta)))
    (if (> (. self d-theta) (. GeometricPattern *d-theta-max*))
        (setv (. self d-theta) (. GeometricPattern *d-theta-max*))))
  
  (defn dec-c [self event]
    (setv (. self c) (dec (. self c)))
    (if (< (. self c) 0)
        (setv (. self c) (% (. self c) (. GeometricPattern *deg*)))))
  
  (defn inc-c [self event]
    (setv (. self c) (inc (. self c)))
    (if (>= (. self c) (. GeometricPattern *deg*))
        (setv (. self c) (% (. self c) (. GeometricPattern *deg*)))))

  (defn inc-phi [self]
    (setv (. self phi) (inc (. self phi)))
    (setv (. self phi) (% (. self phi) (. GeometricPattern *deg*))))
  
  (defn fetch-points [self]
    "Get the coordinates of a point"
    (setv (. self points)
          (lfor theta (range 0 (. GeometricPattern *deg*) (. self d-theta))
                (, (+ (+ (. self center-x) (* (. self r) (cos (radians theta))))
                      (* (. self a) (cos (radians (+ (. self phi) (* (. self c) theta))))))
                   (+ (+ (. self center-y) (* (. self r) (sin (radians theta))))
                      (* (. self a) (sin (radians (+ (. self phi) (* (. self c) theta)))))))))

    ;;Add the first point to the end of the list to close the path
    ((. self points append) (. self points [0]))))


(defclass Simulator []
  (setv *ms* 16)
  
  (defn --init-- [self width height]
    (setv (. self width) width)
    (setv (. self height) height)

    ;;Window and canvas initialization
    (setv (. self window) ((. tk Tk)))
    ((. self window title) :string "Sample Trig Function on Tkinter")
    ((. self window resizable) :width False :height False)
    (setv (. self canvas)
          ((. tk Canvas) (. self window)
                         :width (. self width)
                         :height (. self height)))

    (setv (. self center-x) (/ width 2))
    (setv (. self center-y) (/ height 2))

    ;;Coordinates of characters to display
    (setv (. self quit-x) 20)
    (setv (. self quit-y) 30)
    (setv (. self r-x) (- width 20))
    (setv (. self r-y) (- height 120))
    (setv (. self a-x) (- width 20))
    (setv (. self a-y) (- height 90))
    (setv (. self c-x) (- width 20))
    (setv (. self c-y) (- height 60))
    (setv (. self d-theta-x) (- width 20))
    (setv (. self d-theta-y) (- height 30))

    (setv (. self gp) (GeometricPattern (. self center-x) (. self center-y)))

    ;;Fill the canvas with the background color (black)
    ((. self canvas create-rectangle) 0
                                      0
                                      (. self width)
                                      (. self height)
                                      :fill "black")
    ((. self draw))
    ((. self canvas pack))

    ;;Key binding settings
    ((. self window bind) "<KeyPress-q>" (. self quit))
    ((. self window bind) "<KeyPress-z>" (. self gp dec-r))
    ((. self window bind) "<KeyPress-a>" (. self gp inc-r))
    ((. self window bind) "<KeyPress-x>" (. self gp dec-a))
    ((. self window bind) "<KeyPress-s>" (. self gp inc-a))
    ((. self window bind) "<KeyPress-c>" (. self gp dec-c))
    ((. self window bind) "<KeyPress-d>" (. self gp inc-c))
    ((. self window bind) "<KeyPress-v>" (. self gp dec-d-theta))
    ((. self window bind) "<KeyPress-f>" (. self gp inc-d-theta)))
  
  (defn quit [self event]
    ((. self window destroy)))
  
  (defn draw [self]

    ;;Drawing of shapes
    ((. self canvas create-line) (. self gp points) :fill "white" :tag "sample")
    
    ;;Character drawing
    ((. self canvas create-text) (. self quit-x)
                                 (. self quit-y)
                                 :text "Quit: q"
                                 :tag "sample"
                                 :font (, "Arial" 12)
                                 :fill "white"
                                 :anchor (. tk W))
    ((. self canvas create-text) (. self r-x)
                                 (. self r-y)
                                 :text ((. "r: Z < {0:03d} > A" format) (. self gp r))
                                 :tag "sample"
                                 :font (, "Arial" 12)
                                 :fill "white"
                                 :anchor (. tk E))
    ((. self canvas create-text) (. self a-x)
                                 (. self a-y)
                                 :text ((. "a: X < {0:03d} > S" format) (. self gp a))
                                 :tag "sample"
                                 :font (, "Arial" 12)
                                 :fill "white"
                                 :anchor (. tk E))
    ((. self canvas create-text) (. self c-x)
                                 (. self c-y)
                                 :text ((. "c: C < {0:03d} > D" format) (. self gp c))
                                 :tag "sample"
                                 :font (, "Arial" 12)
                                 :fill "white"
                                 :anchor (. tk E))
    ((. self canvas create-text) (. self d-theta-x)
                                 (. self d-theta-y)
                                 :text ((. "d-theta: V < {0:03d} > F" format) (. self gp d-theta))
                                 :tag "sample"
                                 :font (, "Arial" 12)
                                 :fill "white"
                                 :anchor (. tk E)))
  
  (defn delete [self]
    "Delete the element with the sample tag"
    ((. self canvas delete) "sample"))
  
  (defn loop [self]
    ;;Clear the previous screen
    ((. self delete))

    ;;Get new state and draw
    ((. self gp inc-phi))
    ((. self gp fetch-points))
    ((. self draw))

    ;;Execute loop after about 16ms
    ((. self window after) (. Simulator *ms*) (. self loop))))


(defn main []
  (setv simulator (Simulator *width* *height*))
  ((. simulator loop))
  ((. simulator window mainloop))
  0)


(when (= --name-- "__main__")
      ((. sys exit) (main)))

Create a batch file to call and execute the above geometric_pattern.gui module. activate the virtual environment so that the `hy``` command and hy module are in the path. Then use the `hy``` command to run the geometric_pattern.gui module. With the -m option, you can specify modules in a package in dot-separated notation.

geometric_pattern.bat


@ECHO OFF
SETLOCAL

SET THISDIR=%~dp0
SET PYTHONPATH=%THISDIR%;%PYTHONPATH%
SET ACTIVATE=%USERPROFILE%\.virtualenvs\venv_hy_geometric_pattern\Scripts\activate.bat

CALL %ACTIVATE%
hy -m geometric_pattern.gui
SET STATUS=%ERRORLEVEL%
CALL deactivate

IF %STATUS%==0 (
  ECHO Done.
  PAUSE
  EXIT /b 0
) ELSE (
  ECHO Error.
  PAUSE
  EXIT /b 1
)

Run

Let's do it.

2019-12-16_15h26_49.png 2019-12-16_15h26_58.png 2019-12-16_15h27_25.png 2019-12-16_15h27_30.png

Yes. It may be fun to change the color and thickness of the path, or change it to another formula.

Summary / Supplement

Recommended Posts

Try GUI programming with Hy
Try programming with a shell!
GUI programming with kivy ~ Part 4 Various buttons ~
GUI programming with kivy ~ Part 5 Creating buttons with images ~
Try scraping with Python.
Asynchronous programming with libev # 2
3. 3. AI programming with Python
Python programming with Atom
Competitive programming with python
Shader programming with pyOpenGL
Try SNN with BindsNET
Asynchronous programming with libev
Linear Programming with PuLP
Try regression with TensorFlow
Programming with Python Flask
Asynchronous programming with libev # 3
Try to solve the programming challenge book with python3
GUI programming with kivy ~ Part 3 Video and seek bar ~
Programming with Python and Tkinter
Try to factorial with recursion
Try function optimization with Optuna
Try using PythonTex with Texpad.
Try normal Linux programming Part 7
Try edge detection with OpenCV
Try implementing RBM with chainer.
Try Google Mock with C
Try an autoencoder with Pytorch
Creating GUI tools with pyinstaller
Try Python output with Haxe 3.2
Try matrix operation with NumPy
Try normal Linux programming Part 2
Try implementing XOR with PyTorch
Try running CNN with ChainerRL
Try various things with PhantomJS
Programming education game with SenseHAT
Try normal Linux programming Part 3
Try Deep Learning with FPGA
Reinforcement learning 5 Try programming CartPole?
[GUI with Python] PyQt5-Layout management-
Try normal Linux programming Part 4
Try running Python with Try Jupyter
Try implementing perfume with Go
Try normal Linux programming Part 6
Try Selenium Grid with Docker
Try face recognition with Python
Try OpenCV with Google Colaboratory
[GUI with Python] PyQt5 -Preparation-
Try machine learning with Kaggle
Try TensorFlow MNIST with RNN
Network programming with Python Scapy
Try building JupyterHub with Docker
Try using folium with anaconda
[GUI with Python] PyQt5 -Paint-
Try scraping with Python + Beautiful Soup
GUI programming in Python using Appjar
Reinforcement learning 13 Try Mountain_car with ChainerRL.
[GUI with Python] PyQt5 -Widget II-
Let's make a GUI with python.
GUI development with Fyne by Go
Try to operate Facebook with Python
[Python] Object-oriented programming learned with Pokemon