Commits

mathematicalcoffee  committed 85b9dbf Merge

merged toon-talk into new bookmark gnome3.6-toon-talk so i can develop on 3.6

  • Participants
  • Parent commits c548e9e, 8107697
  • Branches gnome3.6

Comments (0)

Files changed (5)

File xpenguins@mathematical.coffee.gmail.com/stylesheet.css

 .xpenguins-load-averaging:loadAveragingActive {
     color: red;
 }
+
+/* toon talking */
+.speech-bubble {
+    border: 1px solid black;
+    background-color: white;
+    color: black;
+    border-radius: .2em;
+    /*font-size: 1.5em;*/
+}

File xpenguins@mathematical.coffee.gmail.com/talk.js

+/* Handles toon speech
+ * TODO:
+ * - On god mode: each toon goes '!' or voltage symbol
+ * - On quit: each toon does a frowny face
+ * - On email: show an email symbol (and so on...)
+ * - read speeches from theme?
+ * - generate random unicode speeches (but all from the same code block/language)
+ */
+const Clutter = imports.gi.Clutter;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const St = imports.gi.St;
+
+var Me;
+try {
+    Me = imports.ui.extensionSystem.extensions['xpenguins@mathematical.coffee.gmail.com'];
+} catch (err) {
+    Me = imports.misc.extensionUtils.getCurrentExtension().imports;
+}
+const Toon = Me.toon;
+const XPUtil = Me.util;
+
+const SPEECHBUBBLE_TIMEOUT = 1000;
+
+const Arrows = {};
+Arrows[Toon.UNASSOCIATED] = '?';
+Arrows[Toon.HERE] = '.';
+Arrows[Toon.LEFT] = '\u2190';
+Arrows[Toon.RIGHT] = '\u2192';
+Arrows[Toon.UP] = '\u2191';
+Arrows[Toon.DOWN] = '\u2193';
+Arrows[Toon.UPLEFT] = '\u2196';
+Arrows[Toon.UPRIGHT] = '\u2197';
+Arrows[Toon.DOWNLEFT] = '\u2199';
+Arrows[Toon.DOWNRIGHT] = '\u2198';
+
+const RandomSpeeches = [
+    'º(•♠•)º',
+    '╿︡O͟-O︠╿',
+    '\u2af7\u00b0\u29ed\u00b0\u2af8', // ⫷ °⧭°⫸
+    '\u2603', // ☃
+    '\u2665', // ♥
+    '\u2699', // ⚘ 
+    '\u266B\u266A', // music
+    function (toon) { // TODO: check renderable...
+        let length = XPUtil.RandInt(3) + 1,
+            speech = [];
+        while (length--) {
+            speech.push(XPUtil.RandIntRange(32, 65535));
+        }
+        speech = String.fromCharCode.apply('', speech);
+        // log(speech);
+        // BIG TODO: utf8 character can't be converted -- avoid these ones.
+        return speech;
+    }
+];
+const Speeches = {
+    walker: {
+        initial: function (toon) { return Arrows[toon.direction] }
+    },
+
+    runner: {
+        initial: function (toon) {
+            switch (toon.direction) {
+                case LEFT:
+                    return '\u219e';
+                    break;
+                case UP: 
+                    return '\u219F';
+                    break;
+                case RIGHT: 
+                    return '\u21a0';
+                    break;
+                case LEFT: 
+                    return '\u21a1';
+                    break;
+                default:
+                    return '';
+                    break;
+            }
+        }
+    },
+    
+    faller: {
+        initial: '\u26A0', // ⚠
+        during: [
+            '\u2602', // ☂
+            '\u203c', // !!
+            '\u2708'  // ✈
+        ]
+    },
+    
+    tumbler: {
+        initial: '\u203c', // !!
+        during: [
+            '\u2047', // ??
+            '\u2602' // ☂
+        ]
+    },
+
+    floater: {
+        initial: '\ufffd', // �
+        during: [
+            '\u2602' // ☂
+        ]
+    },
+
+
+    climber: {
+        initial: '\u21E7' // ⇧
+    },
+
+    explosion: {
+        initial: '\u2639', // :(
+        during: [
+            '\u2620', // ☠
+            '\u2522', // ☢
+            '\u2623'  // ☣
+        ]
+    },
+
+    zapped: {
+        initial: '\u26A1'
+    },
+    
+    squashed: {
+    },
+
+    splatted: {
+        initial: 'ow!'
+    },
+
+    angel: {
+        initial: '',
+        during: [ // TODO: string these together randomly?
+            '\u2669', // crotchet
+            '\u266A', // quaver
+            '\u266B', // beamed quavers
+            '\u266C', // beamed sixteenths
+            '\u266C' 
+        ]
+    }
+};
+
+function SpeechBubble() {
+    this._init.apply(this, arguments);
+}
+
+SpeechBubble.prototype = {
+    _init: function (toon) {
+        this.toon = toon;
+        this._timeoutId = 0;
+        this.actor = new St.Label({
+            style_class: 'speech-bubble'
+        });
+        this.actor._delegate = this;
+
+        /* add to stage */
+        global.stage.add_actor(this.actor);
+
+        /* bind to the toon's position: can't set unequal offsets in each direction? */
+        this.constrainX = new Clutter.BindConstraint({
+            source: toon.actor,
+            coordinate: Clutter.BindCoordinate.X
+        });
+
+        this.constrainY = new Clutter.BindConstraint({
+            source: toon.actor,
+            coordinate: Clutter.BindCoordinate.Y,
+            offset: 0
+        });
+        this.actor.add_constraint(this.constrainX);
+        this.actor.add_constraint(this.constrainY);
+    },
+
+    show: function (message) {
+        if (this._timeoutId) {
+            Mainloop.source_remove(this._timeoutId);
+        }
+        this.actor.text = message; // bummer!
+
+        // position speech bubble so it trails them...
+        let xoff = this.toon.data.width/2;
+        switch(this.toon.direction) {
+            case Toon.DOWNLEFT:
+            case Toon.UPLEFT:
+            case Toon.LEFT:
+                xoff *= 1.8; // .9 * this.toon.data.width
+                break;
+            case Toon.DOWNRIGHT:
+            case Toon.UPRIGHT:
+            case Toon.RIGHT:
+                xoff = -this.actor.width*.9;
+        }
+        // TODO: how to check this is fully allocated (width, height)
+        this.constrainY.set_offset(-this.actor.height);
+        this.constrainX.set_offset(xoff);
+        this._timeoutId = Mainloop.timeout_add(SPEECHBUBBLE_TIMEOUT,
+            Lang.bind(this, function () {
+                this.actor.hide();
+                this._timeoutId = 0;
+                return false;
+            }));
+        this.actor.show();
+        // TODO: a line or bubbles connecting the speech bubble to the toon
+    },
+
+    /* a call to hide will hide immediately, cancelling any timeout */
+    hide: function () {
+        if (this._timeoutId) {
+            Mainloop.source_remove(this._timeoutId);
+            this._timeoutId = 0;
+        }
+        this.actor.hide();
+    },
+
+    destroy: function () {
+        // remove any timeouts
+        this.hide();
+        // destroy (which removes from stage)
+        this.actor.destroy();
+    }
+};

File xpenguins@mathematical.coffee.gmail.com/toon.js

 const INVULNERABLE = (1 << 1);
 const NOBLOCK = (1 << 2);
 
+const Talk = Me.imports.talk; // Talk requires the constants in Toon... unless I
+                      // put them in this order it screws up (?!?!)
+
 /********************************************************************
  * The Toon structure describes the properties of a particular toon,
  * such as its location and speed.
 
         this.data = null; /* reference to ToonData[this.genus][this.type] */
 
+        this.speechBubble = new Talk.SpeechBubble(this);
+
         // UGLY way to pass in the theme/toon data/stage info/parameters.
         /* needs: XPenguinsWindow's width, height; ToonData; toon_windows;
          * various configuration options.
         }
         XPUtil.DEBUG('  toon changing from %s to %s'.format(this.type, type));
         this.setGenusAndType(this.genus, type, direction, gravity);
+        this.speak(Talk.Speeches[this.type] ? Talk.Speeches[this.type].initial : '');
     },
 
     /* Change a toons genus and type and activate it. */
     },
 
     /**** MORPHING FUNCTIONS ****/
+    /* TODO:
+     * - position based on direction
+     * - unicode chars/pics in the speech bubble
+     * - add to windowClone? stage? do that externally?
+     *   onExit: count down 3..2..1 ! (lemmings) (\u278{C,B,A})
+     *
+     * \u2048 ?!
+     * \u203c !!
+     *
+     * The speech bubble needs to follow the toon, because (e.g.) in the
+     *  case of walking toons.
+     */
+    speak: function (speech) {
+        if (typeof (speech) === 'function') {
+            speech = speech(this);
+        }
+        if (!speech || !speech.length) {
+            return;
+        }
+
+        this.speechBubble.show(speech);
+    },
+
+    isSpeaking: function () {
+        return this.speechBubble.actor.visible;
+    },
+
     /* Turn a penguin into a climber */
     // __xpenguins_make_climber
     makeClimber: function () {
 
     destroy: function () {
         this.actor.destroy();
+        this.speechBubble.destroy();
+        this.speechBubble = null;
     }
 }); // Toon.prototype
 

File xpenguins@mathematical.coffee.gmail.com/util.js

     return Math.floor(Math.random() * max);
 }
 
+
+/* random int from min to max inclusive */
+function RandIntRange(min, max) {
+    return RandInt(max - min + 1) + min;
+}
+
 /* Utility logging function */
 function LOG() {
     let msg = arguments[0];

File xpenguins@mathematical.coffee.gmail.com/xpenguins.js

 const WindowClone = Me.imports.windowClone;
 const WindowListener = Me.imports.windowListener;
 const XPUtil = Me.imports.util;
+const Talk = Me.imports.talk;
 
 /* constants */
 const PENGUIN_MAX = 50; /* per theme */
                         }
                     } // switch(toon.type)
                 } // whether sqashed
+                /* there is a small chance a toon will say something, if it
+                 * isn't already saying something */
+                // little under 20%
+                if (!XPUtil.RandInt(100) && !toon.isSpeaking()) {
+                    let speeches = Talk.Speeches[toon.type] ? Talk.Speeches[toon.type].during : null,
+                        speech = null;
+                    if (!speeches || !speeches.length) {
+                        speeches = Talk.RandomSpeeches;
+                    }
+                    speech = speeches[XPUtil.RandInt(speeches.length)];
+                    if (typeof speech === 'function') {
+                        speech = speech(toon);
+                    }
+                    toon.speak(speech);
+                }
+
             } // toon state
 
             /* update clip frame to advance animation */