Animation: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|J}}: remove superfluous xywh=.)
(→‎{{header|J}}: align comments)
Line 374: Line 374:
DIRECTION =: 1 NB. initial direction is right -->
DIRECTION =: 1 NB. initial direction is right -->
NB. Define GUI
NB. GUI Definition
ANIM =: noun define
ANIM =: noun define
pc anim nomax nosize;pn "Basic Animation in J";
pc anim nomax nosize;pn "Basic Animation in J";
Line 381: Line 381:
rem form end;
rem form end;
)
)

NB. Function to start animation
NB. Function to start animation
anim_run =: verb define
anim_run =: verb define
NB. Initialize GUI
wd ANIM NB. Initialize GUI
wd 'pshow; timer ',":TIMER_INTERVAL NB. Show GUI and start animation timer
wd ANIM
)


NB. Show GUI and start animation timer
wd 'pshow; timer ',":TIMER_INTERVAL
)
NB. Async timer event, called every TIMER_INTERVAL milliseconds.
NB. Async timer event, called every TIMER_INTERVAL milliseconds.
sys_timer_z_ =: verb define
sys_timer_z_ =: verb define
NB. Rotate MESSAGE (left or right depending on DIRECTION).
MESSAGE =: DIRECTION |. MESSAGE NB. Rotate MESSAGE according to DIRECTION
isiMsg '' NB. Update GUI
MESSAGE =: DIRECTION |. MESSAGE
NB. Update GUI
isiMsg ''
)
)

NB. Callback from user mouse click.
NB. Callback from user mouse click.
anim_isi_mbldown =: verb define
anim_isi_mbldown =: verb define
NB. Reverse direction of animation
DIRECTION =: - DIRECTION NB. Reverse direction
DIRECTION =: - DIRECTION
)
)

NB. Clears & repaints GUI
NB. Clears & repaints GUI
isiMsg =: verb define
isiMsg =: verb define
wd'psel anim'
wd'psel anim'
glclear'' NB. clear out old drawing
glclear'' NB. clear out old drawing
anim_isi_paint'' NB. new drawing
anim_isi_paint'' NB. new drawing
glpaint'' NB. copy to screen
glpaint'' NB. copy to screen
)
)
NB. Async paint event, called when GUI needs repainting
NB. Async paint event, called when GUI needs repainting
Line 425: Line 416:
NB. Async teardown function, called when user closes GUI.
NB. Async teardown function, called when user closes GUI.
anim_close =: verb define
anim_close =: verb define
NB. Shut down timer.
wd 'timer 0; pclose; reset;' NB. Shut down timer
erase 'sys_timer_z_'
wd 'timer 0; pclose; reset;'
erase'sys_timer_z_'
)
)

Revision as of 04:19, 10 December 2009

Task
Animation
You are encouraged to solve this task according to the task description, using any language you may know.

Animation is the foundation of a great many parts of graphical user interfaces, including both the fancy effects when things change used in window managers, and of course games. The core of any animation system is a scheme for periodically changing the display while still remaining responsive to the user. This task demonstrates this.

Create a window containing the string "Hello World! " (the trailing space is significant). Make the text appear to be rotating right by periodically removing one letter from the end of the string and attaching it to the front. When the user clicks on the text, it should reverse its direction.

AutoHotkey

<lang AutoHotkey>message := "Hello World! " Gui, Add, Text, vmyedit , %message% Gui, +AlwaysOnTop +Disabled -SysMenu +Owner Gui, Show animate() Return

  1. IfWinActive animation.ahk

~LButton::

 WinGetPos ,,, width, height
 MouseGetPos, x, y
 If x And y
 If (x < width) And (y < height)
 reverse := !reverse
 return
  1. IfWinActive

animate() {

 Global 
 Loop,
 {
   Sleep, 200
   If reverse
     message := SubStr(message, 2, StrLen(message)) . SubStr(message,1, 1)
   Else
     message := SubStr(message, StrLen(message), 1) . SubStr(message, 1, StrLen(message) - 1)
   GuiControl,,myedit, %message%
 }

}</lang>

C

Library: GTK

(NB: implicitly, through GTK, it uses also Pango library) <lang c>#include <stdlib.h>

  1. include <string.h>
  2. include <gtk/gtk.h>

const gchar *hello = "Hello World! "; gint direction = -1; gint cx=0; gint slen=0;

GtkLabel *label;

void change_dir(GtkLayout *o, gpointer d) {

 direction = -direction;

}

gchar *rotateby(const gchar *t, gint q, gint l) {

 gint i, cl = l, j;
 gchar *r = malloc(l+1);
 for(i=q, j=0; cl > 0; cl--, i = (i + 1)%l, j++)
   r[j] = t[i];
 r[l] = 0;
 return r;

}

gboolean scroll_it(gpointer data) {

 if ( direction > 0 )
   cx = (cx + 1) % slen;
 else
   cx = (cx + slen - 1 ) % slen;
 gchar *scrolled = rotateby(hello, cx, slen);
 gtk_label_set_text(label, scrolled);
 free(scrolled);
 return TRUE;

}


int main(int argc, char **argv) {

 GtkWidget *win;
 GtkButton *button;
 PangoFontDescription *pd;
 gtk_init(&argc, &argv);
 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 gtk_window_set_title(GTK_WINDOW(win), "Basic Animation");
 g_signal_connect(G_OBJECT(win), "delete-event", gtk_main_quit, NULL);
 label = (GtkLabel *)gtk_label_new(hello);
 // since we shift a whole character per time, it's better to use
 // a monospace font, so that the shifting seems done at the same pace
 pd = pango_font_description_new();
 pango_font_description_set_family(pd, "monospace");
 gtk_widget_modify_font(GTK_WIDGET(label), pd);
 button = (GtkButton *)gtk_button_new();
 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(label));
 gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(button));
 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(change_dir), NULL);
 slen = strlen(hello);
 g_timeout_add(125, scroll_it, NULL);
 
 gtk_widget_show_all(GTK_WIDGET(win));
 gtk_main();
 return 0;

}</lang>

C#

<lang csharp>using System; using System.Drawing; using System.Windows.Forms;

namespace BasicAnimation {

 class BasicAnimationForm : Form
 {
   bool isReverseDirection;
   Label textLabel;
   Timer timer;
   internal BasicAnimationForm()
   {
     this.Size = new Size(150, 75);
     this.Text = "Basic Animation";
     textLabel = new Label();
     textLabel.Text = "Hello World! ";
     textLabel.Location = new Point(3,3);
     textLabel.AutoSize = true;
     textLabel.Click += new EventHandler(textLabel_OnClick);
     this.Controls.Add(textLabel);
     timer = new Timer();
     timer.Interval = 500;
     timer.Tick += new EventHandler(timer_OnTick);
     timer.Enabled = true;
     isReverseDirection = false;
   }
   private void timer_OnTick(object sender, EventArgs e)
   {
     string oldText = textLabel.Text, newText;
     if(isReverseDirection)
       newText = oldText.Substring(1, oldText.Length - 1) + oldText.Substring(0, 1);
     else
       newText = oldText.Substring(oldText.Length - 1, 1) + oldText.Substring(0, oldText.Length - 1);
     textLabel.Text = newText;
   }
   private void textLabel_OnClick(object sender, EventArgs e)
   {
     isReverseDirection = !isReverseDirection;
   }
 }
  class Program
  {
     static void Main()
     {

Application.Run(new BasicAnimationForm());

     }
  }

}</lang>

Clojure

Clojure is a JVM language so this example uses Swing, and illustrates Clojure's platform integration. <lang lisp>(import '(javax.swing JFrame JLabel)) (import '(java.awt.event MouseAdapter))

(def text "Hello World! ") (def text-ct (count text)) (def rotations

 (vec
   (take text-ct
     (map #(apply str %)
       (partition text-ct 1 (cycle text))))))

(def pos (atom 0)) ;position in rotations vector being displayed (def dir (atom 1)) ;direction of next position (-1 or 1)

(def label (JLabel. text))

(.addMouseListener label

 (proxy [MouseAdapter] []
   (mouseClicked [evt] (swap! dir -))))

(defn animator []

 (while true
   (Thread/sleep 100)
   (swap! pos #(-> % (+ @dir) (mod text-ct)))
   (.setText label (rotations @pos))))

(doto (JFrame.)

 (.add label)
 (.pack)
 (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
 (.setVisible true))
 

(future-call animator) ;simple way to run animator on a separate thread</lang>

Common Lisp

The ltk package provides a lisp interface to Tk for creating graphical interfaces. Assuming ltk has been installed somewhere the following will work as per the Tcl example.

Library: Tk

<lang lisp>(use-package :ltk)

(defparameter *message* "Hello World! ") (defparameter *direction* :left) (defun animate (label)

 (let* ((n (length *message*))
        (i (if (eq *direction* :left) 0 (1- n)))
        (c (string (char *message* i))))
   (if (eq *direction* :left)
       (setq *message* (concatenate 'string (substring *message* 1 n) (list c)))
     (setq *message* (concatenate 'string (list c) (substring *message* 0 (1- n)))))
   (setf (ltk:text label) *message*)
   (ltk:after 125 (lambda () (animate label)))))
       

(defun basic-animation ()

 (ltk:with-ltk ()
     (let* ((label (make-instance 'label 
                                  :font "Courier 14")))
       (setf (text label) *message*)
       (ltk:bind label "<Button-1>"
                 (lambda (event)
                   (declare (ignore event))
                   (cond
                    ((eq *direction* :left) (setq *direction* :right))
                    ((eq *direction* :right) (setq *direction* :left)))))
       (ltk:pack label)
       (animate label)
       (ltk:mainloop))))

(basic-animation)</lang>

D

Library: QD

uses

Library: SDL
Library: SDL_ttf
Library: tools

<lang d>module test26;

import qd, SDL_ttf, tools.time;

void main() {

 screen(320, 200);
 auto last = sec();
 string text = "Hello World! ";
 auto speed = 0.2;
 int dir = true;
 while (true) {
   cls;
   print(10, 10, Bottom|Right, text);
   if (sec() - last > speed) {
     last = sec();
     if (dir == 0) text = text[$-1] ~ text[0 .. $-1];
     else text = text[1 .. $] ~ text[0];
   }
   flip; events;
   if (mouse.clicked
     && mouse.pos in display.select(10, 10, 100, 20)
   ) dir = !dir;
 }

}</lang>

E

Works with: E-on-Java

(Java Swing; tested on Mac OS X 10.5.7)

<lang e># State var text := "Hello World! " var leftward := false

  1. Window

def w := <swing:makeJFrame>("RC: Basic Animation")

  1. Text in window

w.setContentPane(def l := <swing:makeJLabel>(text)) l.setOpaque(true) # repaints badly if not set! l.addMouseListener(def mouseListener {

   to mouseClicked(_) {
       leftward := !leftward
   }
   match _ {}

})

  1. Animation

def anim := timer.every(100, fn _ { # milliseconds

   def s := text.size()
   l.setText(text := if (leftward) {
       text(1, s) + text(0, 1)
   } else {
       text(s - 1, s) + text(0, s - 1)
   })

})

  1. Set up window shape and close behavior

w.pack() w.setLocationRelativeTo(null) w.addWindowListener(def windowListener {

   to windowClosing(_) { anim.stop() } 
   match _ {}

})

  1. Start everything

w.show() anim.start()</lang>

Text-only version (no Java dependency; no clicking, use reverse() and stop() to control):

<lang e>def [reverse, stop] := {

   var text := "Hello World! "
   var leftward := false
   def anim := timer.every(100, fn _ { # milliseconds
       def s := text.size()
       text := if (leftward) {
           text(1, s) + text(0, 1)
       } else {
           text(s - 1, s) + text(0, s - 1)
       }
       print("\b" * s, text)
   })
   print("\n", text)
   anim.start()
   [def _() { leftward := !leftward; null }, anim.stop]

}</lang>

Factor

<lang Factor>! Copyright (C) 2009 Jon Harper ! See http://factorcode.org/license.txt for BSD license. USING: accessors alarms calendar kernel models sequences ui ui.gadgets ui.gadgets.labels ui.gestures ; FROM: models => change-model ; IN: rosetta.animation

CONSTANT: sentence "Hello World! "

TUPLE: animated-label < label-control reversed alarm ;

<animated-label> ( model -- <animated-model> )
   sentence animated-label new-label swap >>model ;
update-string ( str reverse -- str )
   [ unclip-last prefix ] [ unclip suffix ] if ;
update-model ( model reversed? -- )
   [ update-string ] curry change-model ;

animated-label

   H{ 
       { T{ button-down } [ [ not ] change-reversed drop ] }
    } set-gestures

M: animated-label graft*

 [ [ [ model>> ] [ reversed>> ] bi update-model ] curry 400 milliseconds every ] keep
 (>>alarm) ;

M: animated-label ungraft*

   alarm>> cancel-alarm ;
main ( -- )
  [ sentence <model> <animated-label> "Rosetta" open-window ] with-ui ;

MAIN: main</lang>

J

<lang j>NB. load gl2 definitions & make accessible from this locale coinsert'jgl2' [ require'gl2 strings'

NB. Configuration MESSAGE =: 'Hello World! ' TIMER_INTERVAL =: 0.5 * 1000 NB. in milliseconds DIRECTION =: 1 NB. initial direction is right -->

NB. GUI Definition ANIM =: noun define

  pc anim nomax nosize;pn "Basic Animation in J";
  xywh 1 1 174 24;cc isi isigraph rightmove bottommove;
  pas 0 0;pcenter;
  rem form end;

)

NB. Function to start animation anim_run =: verb define wd ANIM NB. Initialize GUI wd 'pshow; timer ',":TIMER_INTERVAL NB. Show GUI and start animation timer )

NB. Async timer event, called every TIMER_INTERVAL milliseconds. sys_timer_z_ =: verb define MESSAGE =: DIRECTION |. MESSAGE NB. Rotate MESSAGE according to DIRECTION isiMsg NB. Update GUI )

NB. Callback from user mouse click. anim_isi_mbldown =: verb define DIRECTION =: - DIRECTION NB. Reverse direction )

NB. Clears & repaints GUI isiMsg =: verb define wd'psel anim' glclear NB. clear out old drawing anim_isi_paint NB. new drawing glpaint NB. copy to screen )

NB. Async paint event, called when GUI needs repainting anim_isi_paint =: verb define glfont'"courier new" 36' gltextxy 0 0 gltext MESSAGE )

NB. Async teardown function, called when user closes GUI. anim_close =: verb define wd 'timer 0; pclose; reset;' NB. Shut down timer erase 'sys_timer_z_' )

NB. Start animation... anim_run </lang>

Java

Library: Swing

<lang java>import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Timer; import java.util.TimerTask; import javax.swing.JFrame; import javax.swing.JLabel;

public class Rotate extends JFrame {

 String text = "Hello World! ";
 JLabel label = new JLabel(text);
 boolean rotRight = true;
 int startIdx = 0;
 public Rotate() {
   label.addMouseListener(new MouseAdapter() {
     @Override
     public void mouseClicked(MouseEvent evt) {
       rotRight = !rotRight;
     }
   });
   add(label);
   pack();
   setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   setVisible(true);
 }
 public static void main(String[] args) {
   final Rotate rot = new Rotate();
   TimerTask task = new TimerTask() {
     public void run() {
       if (rot.rotRight) {
         rot.startIdx++;
         if (rot.startIdx >= rot.text.length()) {
           rot.startIdx -= rot.text.length();
         }
       } else {
         rot.startIdx--;
         if (rot.startIdx < 0) {
           rot.startIdx += rot.text.length();
         }
       }
       rot.label.setText(getRotatedText(rot.text, rot.startIdx));
     }
   };
   Timer timer = new Timer(false);
   timer.schedule(task, 0, 500);
 }
 public static String getRotatedText(String text, int startIdx) {
   String ret = "";
   int i = startIdx;
   do {
     ret += text.charAt(i) + "";
     i++;
     i = i % text.length();
   } while (i != startIdx);
   return ret;
 }

}</lang>

JavaScript + HTML

<lang javascript><html> <head>

   <title>RC: Basic Animation</title>
   <script type="text/javascript">
       function animate(id) {
           var element = document.getElementById(id);
           var textNode = element.childNodes[0]; // assuming no other children
           var text = textNode.data;
           var reverse = false;
           
           element.onclick = function () { reverse = !reverse; };
           
           setInterval(function () {
               if (reverse)
                   text = text.substring(1) + text[0];
               else
                   text = text[text.length - 1] + text.substring(0, text.length - 1);
               textNode.data = text;
           }, 100);
       }
   </script>

</head> <body onload="animate('target')">

Hello World! 

</body> </html></lang>

JavaScript + SVG

<lang javascript><svg xmlns="http://www.w3.org/2000/svg"

    width="100" height="40">
   <script type="text/javascript">
       function animate(element) {
           var textNode = element.childNodes[0]; // assuming no other children
           var text = textNode.data;
           var reverse = false;
           element.onclick = function () { reverse = !reverse; };
           
           setInterval(function () {
               if (reverse)
                   text = text.substring(1) + text[0];
               else
                   text = text[text.length - 1] + text.substring(0, text.length - 1);
               textNode.data = text;
           }, 100);
       }
   </script>
 
   <rect width="100" height="40" fill="yellow"/>
   <text x="2" y="20" onload="animate(this);">Hello World! </text>

</svg></lang>

Works with: UCB Logo

<lang logo> to rotate.left :thing

 output lput first :thing butfirst :thing

end to rotate.right :thing

 output fput last :thing butlast :thing

end

make "text "|Hello World! | make "right? "true

to step.animation

 label :text			; graphical
 ; type char 13  type :text	; textual
 wait 6			; 1/10 second
 if button <> 0 [make "right? not :right?]
 make "text ifelse :right? [rotate.right :text] [rotate.left :text]

end

hideturtle until [key?] [step.animation] </lang>

Mathematica

<lang Mathematica> mystring = "Hello World! "; Scroll[str_, dir_] := StringJoin @@ RotateLeft[str // Characters, dir]; GiveString[dir_] := (mystring = Scroll[mystring, dir]); CreateDialog[{

  DynamicModule[{direction = -1}, 
   EventHandler[
    Dynamic[TextCell[
      Refresh[GiveString[direction], UpdateInterval -> 1/8]], 
     TrackedSymbols -> {}], {"MouseClicked" :> (direction *= -1)}]]
  }];

</lang>

REBOL

<lang REBOL>REBOL [ Title: "Basic Animation" Author: oofoe Date: 2009-12-06 URL: http://rosettacode.org/wiki/Basic_Animation ]

message: "Hello World! " how: 1

roll: func [ "Shifts a text string right or left by one character." text [string!] "Text to shift." direction [integer!] "Direction to shift -- right: 1, left: -1." /local h t ][ either direction > 0 [ h: last text t: copy/part text ((length? text) - 1) ][ h: copy skip text 1 t: text/1 ] rejoin [h t] ]

This next bit specifies the GUI panel. The window will have a
gradient backdrop, over which will be composited the text, in a
monospaced font with a drop-shadow. A timer (the 'rate' bit) is set
to update 24 times per second. The 'engage' function in the 'feel'
block listens for events on the text face. Time events update the
animation and mouse-down change the animation's direction.

view layout [ backdrop effect [gradient 0x1 coal black]

vh1 as-is message ; 'as-is' prevents text trimming. font [name: font-fixed] rate 24 feel [ engage: func [f a e] [ case [ 'time = a [set-face f message: roll message how] ; Animate. 'down = a [how: how * -1] ; Change direction. ] ] ] ]</lang>

Ruby

Translation of: Tcl
Library: Ruby/Tk

<lang ruby>require 'tk' $str = TkVariable.new("Hello World! ") $dir = :right

def animate

 $str.value = shift_char($str.value, $dir)
 $root.after(125) {animate}

end

def shift_char(str, dir)

 case dir
 when :right then str[-1,1] + str[0..-2]
 when :left  then str[1..-1] + str[0,1]
 end

end

$root = TkRoot.new("title" => "Basic Animation")

TkLabel.new($root) do

 textvariable $str
 font "Courier 14"
 pack {side 'top'}
 bind("ButtonPress-1") {$dir = {:right=>:left,:left=>:right}[$dir]}

end

animate Tk.mainloop</lang>

SVG (no scripts)

Works with: Batik version 1.7

This animation is defined as a smooth movement rather than by moving whole characters, because that is more natural in SVG (without scripting); by characters would require 13 different text elements displayed in sequence.

<lang xml><svg xmlns="http://www.w3.org/2000/svg" width="100" height="30">

   <g id="all">
       <rect width="100%" height="100%" fill="yellow"/>
       <g style="font: 18 'Times New Roman', serif; 
                 fill: black;
                 stroke: white; stroke-width: 0.001; /* workaround for Batik oddity */ ">
           <text x="0" y="20" textLength="95">Hello World!</text>
           <text x="-100" y="20" textLength="95">Hello World!</text>
           <animateMotion restart="whenNotActive" repeatCount="indefinite" dur="2s"
                          begin="0s;all.click" end="all.click"
                          from="0,0"   by="100,0"/>
           <animateMotion restart="whenNotActive" repeatCount="indefinite" dur="2s"
                          begin="all.click" end="all.click"
                          from="100,0" by="-100,0"/>
       </g>
   </g>

</svg></lang>

(Does not work in Safari 4.0.2 because it apparently does not implement toggled animations correctly (see spec). Dreadful workaround: set the two animations to id="a" begin="0s;all.click" end="all.mousedown" and begin="a.end" end="all.click", respectively.)

Tcl

Library: Tk

<lang tcl>package require Tk set s "Hello World! " set dir 0

  1. Periodic animation callback

proc animate {} {

   global dir s
   if {$dir} {
       set s [string range $s 1 end][string index $s 0]
   } else {
       set s [string index $s end][string range $s 0 end-1]
   }
   # We will run this code ~8 times a second (== 125ms delay)
   after 125 animate

}

  1. Make the label (constant width font looks better)

pack [label .l -textvariable s -font {Courier 14}]

  1. Make a mouse click reverse the direction

bind .l <Button-1> {set dir [expr {!$dir}]}

  1. Start the animation

animate</lang>

TI-89 BASIC

The requirements contain "When the user clicks on the text…". The TI-89 does not have a graphical cursor, so just for the sake of overdoing it, and to have a little more complex animation (overlapping objects), this program implements one. Use the arrow keys and ENTER to control the cursor.

rcanimat()
Prgm
  Local leftward,s,i,k,x,y

  false → leftward
  "Hello World! " → s
  0 → k      © last keypress found
  6*3 → x    © cursor position
  5 → y

  ClrIO
  While k ≠ 4360 and k ≠ 277 and k ≠ 264  © QUIT,HOME,ESC keys

    © Handle Enter key
    If k = 13 Then
      If x ≥ 40 and x < 40+6*dim(s) and y ≥ 25 and y < 35 Then © On text?
        not leftward → leftward
      ElseIf x ≥ 5 and x < 5+6*dim("[Quit]") and y ≥ 55 and y < 65 Then © On quit?
        Exit
      EndIf
    EndIf

    © Cursor movement keys
    If k=338 or k=340 or k=344 or k=337 or k=339 or k=342 or k=345 or k=348 Then
      Output y, x, " " © Blank old cursor pos
      If     k = 338 or k = 339 or k = 342 Then: y-6→y
      ElseIf k = 344 or k = 345 or k = 348 Then: y+6→y :EndIf
      If     k = 337 or k = 339 or k = 345 Then: x-6→x
      ElseIf k = 340 or k = 342 or k = 348 Then: x+6→x :EndIf
      min(max(y, 0), 64)→y
      min(max(x, 0), 152)→x
    EndIf

    © Drawing
    Output 0, 0, "Use arrows, ENTER key"
    Output 60, 5, "[Quit]"
    Output 30, 40, s
    Output y, x, "Ŵ"              © should be diamond symbol

    © Animation
    If leftward Then
      right(s, dim(s)-1) & left(s, 1) → s
    Else
      right(s, 1) & left(s, dim(s)-1) → s
    EndIf

    0 → i
    getKey() → k                  © reads most recent keypress or 0
    While i < 2 and k = 0         © Delay loop. Better solution?
      getKey() → k
      i + 1 → i
    EndWhile

  EndWhile
  DispHome
EndPrgm