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
- IfWinActive animation.ahk
~LButton::
WinGetPos ,,, width, height MouseGetPos, x, y If x And y If (x < width) And (y < height) reverse := !reverse return
- 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
(NB: implicitly, through GTK, it uses also Pango library) <lang c>#include <stdlib.h>
- include <string.h>
- 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>
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.
<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
uses
<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
(Java Swing; tested on Mac OS X 10.5.7)
<lang e># State var text := "Hello World! " var leftward := false
- Window
def w := <swing:makeJFrame>("RC: Basic Animation")
- 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 _ {}
})
- 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) })
})
- Set up window shape and close behavior
w.pack() w.setLocationRelativeTo(null) w.addWindowListener(def windowListener {
to windowClosing(_) { anim.stop() } match _ {}
})
- 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>
Java
<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.substr(1, text.length) + text[0]; else text = text[text.length - 1] + text.substr(0, text.length - 1); textNode.data = text; }, 100); } </script> <style type="text/css"> #target { font-family: monospace; } </style>
</head> <body onload="animate('target')">
</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.substr(1, text.length) + text[0]; else text = text[text.length - 1] + text.substr(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>
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>
Ruby
<lang ruby>require 'tk' $str = TkVariable.new("Hello World! ") $dir = 0
def animate
t = $str.value $str.value = if $dir == 0 t[1..-1] + t[0].chr else t[-1].chr + t[0..-2] end $root.after(125) {animate}
end
$root = TkRoot.new("title" => "Basic Animation")
TkLabel.new($root) do
textvariable $str font "Courier 14" pack {side 'top'} bind("ButtonPress-1") {$dir = ($dir + 1) % 2}
end
animate Tk.mainloop</lang>
SVG (no scripts)
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
<lang tcl>package require Tk set s "Hello World! " set dir 0
- 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
}
- Make the label (constant width font looks better)
pack [label .l -textvariable s -font {Courier 14}]
- Make a mouse click reverse the direction
bind .l <Button-1> {set dir [expr {!$dir}]}
- Start the animation
animate</lang>