Browse Source

Long text implemented but not tested, moving GUI setup to ui.c but move is not complete yet.

Beoran 7 years ago
parent
commit
137271ef69
9 changed files with 793 additions and 59 deletions
  1. 28 0
      include/ui.h
  2. 5 1
      include/zori/zori.h
  3. 44 1
      include/zori/zori_longtext.h
  4. 1 38
      src/state.c
  5. 191 0
      src/ui.c
  6. 15 1
      src/zori/zori.c
  7. 499 0
      src/zori/zori_longtext.c
  8. 9 6
      src/zori/zori_screen.c
  9. 1 12
      test/test_ui.c

+ 28 - 0
include/ui.h

@@ -1,14 +1,42 @@
 #ifndef ui_H_INCLUDED
 #define ui_H_INCLUDED
 
+
+
 /* Data struct for the particular GUI state */
 struct ui_state {
   zori_id screen;
   zori_id console;
+  
+  /* main page/menu */
+  struct ui_state_main {
+    zori_id page;
+    zori_id menu;
+    
+    /* menu buttons */
+    struct ui_state_main_buttons { 
+      zori_id resume;
+      zori_id new;
+      zori_id settings;      
+    } button;
+  } main;
+  
+  /* HUD page and widgets */
+  struct ui_state_hud {
+    zori_id page;    
+  } hud;
+  
+  /* Settings page and widgets */
+  struct ui_state_settings {
+    zori_id page;    
+    zori_id menu;    
+  } settings;
+  
 };
 
 
 void ui_setup(void);
+void ui_state_init(struct ui_state * ui);
 
 
 

+ 5 - 1
include/zori/zori.h

@@ -284,11 +284,12 @@ static inline void * zori_event_set_data(union zori_event * event, void * data)
   return event->any.data = data;
 }
 
-/* A style part is a color, image and font applied to a part of the GUI. */
+/* A style part is a color, image, font, and font flags applied to a part of the GUI. */
 struct zori_stylepart {
   zori_color       color;
   zori_bitmap    * bitmap;
   zori_font      * font;
+  int              font_flags; 
 };
 
 /* A style is a set of style parts for different parts of the GUI. */
@@ -589,6 +590,9 @@ int zori_widget_x(struct zori_widget *widget);
 int zori_widget_y(struct zori_widget *widget);
 int zori_xy_inside_widget_p(struct zori_widget * widget, double x, double y);
 
+zori_font * zori_widget_text_font(struct zori_widget * widget);
+zori_font * zori_text_font(zori_id id);
+
 
 void zori_widget_drawroundframe(struct zori_widget *self);
 

+ 44 - 1
include/zori/zori_longtext.h

@@ -3,6 +3,8 @@
 
 #include "zori.h"
 
+#define ZORI_WIDGET_TYPE_LONGTEXT ZORI_WIDGET_TYPE('z','l','t','x')
+
 struct zori_longtext {
   struct zori_widget widget;
   int                align;
@@ -14,10 +16,51 @@ struct zori_longtext {
   int page_lines;                      
   /* Text "window" size for one "page" of text in amount of lines. */
   int paused;
+  double delay;
   double delay_total;
 };
+ 
 
-  
+struct zori_longtext *zori_widget_to_longtext(struct zori_widget *widget);
+struct zori_longtext *zori_longtext_set(struct zori_longtext *longtext, const zori_string *text);
+struct zori_longtext *zori_longtext_set_cstr(struct zori_longtext *longtext, const char *cstr);
+struct zori_longtext *zori_longtext_init_cstr(struct zori_longtext *longtext, const char *cstr);
+void zori_longtext_done(struct zori_longtext *longtext);
+int zori_longtext_page(struct zori_longtext *longtext);
+int zori_longtext_last_page(struct zori_longtext *longtext);
+int zori_longtext_page_(struct zori_longtext *longtext, int page);
+int zori_longtext_at_end(struct zori_longtext *longtext);
+void zori_longtext_update_longtext(struct zori_longtext *longtext, double dt);
+int zori_longtext_longtext_at_end(struct zori_longtext *longtext);
+void zori_longtext_draw_text(struct zori_longtext *longtext);
+void zori_longtext_draw_partial_text(struct zori_longtext *longtext);
+void zori_draw_longtext(struct zori_longtext *longtext);
+zori_id zori_set_line_stop(zori_id index, int stop);
+zori_id zori_line_start_(zori_id index, int start);
+int zori_delay_(zori_id index, double delay);
+int zori_line_stop(int index);
+int zori_line_start(int index);
+double zori_delay(int index);
+int zori_page_lines_(zori_id index, int lines);
+int zori_page_lines(int index);
+int zori_paused_(zori_id index, int paused);
+int zori_paused(zori_id index);
+int zori_page(zori_id index);
+int zori_last_page(zori_id index);
+int zori_at_end(zori_id index);
+int zori_page_(zori_id index, int page);
+int zori_next_page(zori_id index);
+int zori_previous_page(int index);
+int zori_longtext_on_mouse_axes(union zori_event *event);
+int zori_longtext_on_mouse_click(union zori_event *event);
+int zori_longtext_on_draw(union zori_event *event);
+struct zori_longtext *zori_longtext_init(struct zori_longtext *longtext, const char *text);
+struct zori_longtext *zori_longtext_new(zori_id id, zori_id parent_id, zori_box *box, const char *text);
+zori_id zori_new_longtext(zori_id id, zori_id parent, zori_box *box, const char *text);
+
+
+ 
+ 
 
 #endif
 

+ 1 - 38
src/state.c

@@ -500,44 +500,7 @@ int state_initjoystick(State * self) {
 
 /* Initialize the GUI for the Eruta engine. */
 State * state_init_gui(State * self, BOOL fullscreen) {
-  /* Set up Zori GUI. */
-  zori_id main_page, main_menu;
-  struct zori_style style;
-  Rebox box;
-  memset(&style, 0, sizeof(style));
-  style.text.font  = self->font;
-  style.text.color = color_rgb(255,255,255);
-  style.back.color = color_rgba(64,0,0, 191);
-  if ( !ZORI_ID_OK_P(zori_start(&style)) ) {
-    return state_errmsg_(self, "Out of memory when allocating GUI.");
-  }
-  
-  self->ui.screen = zori_new_screen(-1, self->display);
-  
-  if(!ZORI_ID_OK_P(self->ui.screen)) {
-    return state_errmsg_(self, "Could not init GUI screen.\n");
-  }
-  
-  main_page = zori_new_page(-1, self->ui.screen);
-  LOG_NOTE("Main page: %d\n", main_page);
-  box = rebox_make(280, 80, 140, 240);  
-  main_menu = zori_new_menu(-1, main_page, &box); 
-  
-  LOG_NOTE("Main menu: %d\n", main_menu);
-  
-  
-  {
-    Rebox box = rebox_make(300, 100, 100, 60);
-    zori_id button = zori_new_button(-1, main_menu, &box, "Button 1");
-    LOG_NOTE("Button: %d\n", button);
-  }
-  {
-    Rebox box = rebox_make(300, 200, 100, 60);
-    zori_id button = zori_new_button(-1, main_menu, &box, "Button 2");
-    LOG_NOTE("Button: %d\n", button);
-  }
-  
-  return self;
+  ui_state_init(&self->ui);
 }
 
 

+ 191 - 0
src/ui.c

@@ -1,13 +1,204 @@
 #include "eruta.h"
 #include "image.h"
+#include "store.h"
 #include "zori.h"
+#include "zori_screen.h"
+#include "zori_console.h"
 #include "zori_button.h"
+#include "zori_page.h"
+#include "zori_menu.h"
 #include "ui.h"
 
 
 /* This file contains the concrete implementation of the Eruta GUI
  * and it's various GUI screens and pages. */
 
+static struct ui_state the_ui_state;
 
+char POEM_TEXT[] = "After mankind's summer, winter suddenly came"
+              "Most reached for the stars, but some remained"
+              "The earth scarred by the Ancients unhostpitable"
+              "The hopes low, and so much wisdom forgotten"
+              "Yet when it seemed the last hour struck"
+              "Our hope returned, a savior arose"
+              "Lord Kei, who by Creator's grace"
+              "Restored the Trees that guard us all from harm"
+              "Thus ushered in a new millennial spring"
+              "Lord Kei, watch over us and protect us"
+              "Until the Travellers return with bounty of the stars";
+
+char INTRO_TEXT[] = "Millennia have passed since mankind first traveled to the moon. "
+               "Civilizations rose as never before, yet to fall again. " 
+               "When all hope seemed lost, the 21 trees sprouted from the earth. " 
+               "They brought mysterious powers that protected and strengthened mankind. "
+               "Hi!\n\n" 
+               "Hello µ world, this is me, 無 life should be\nfun for everyone!";
+
+char INTRO_TEXT2[] = "Something happened, and I was changed!";               
+
+zori_id ui_state_make_sub_menu(zori_id parent, int x, int y, int w, int bh); 
+
+void ui_state_init(struct ui_state * ui) { }
+
+#ifdef COMMENT_
+
+/* Set up Zori GUI. */
+
+void ui_state_init(struct ui_state * ui, zori_displkay * display) { 
+
+  struct zori_style style;
+  Rebox box;
+  memset(&style, 0, sizeof(style));
+  style.text.font  = self->font;
+  style.text.color = color_rgb(255,255,255);
+  style.back.color = color_rgba(64,0,0, 191);
+  if ( !ZORI_ID_OK_P(zori_start(&style)) ) {
+    return state_errmsg_(self, "Out of memory when allocating GUI.");
+  }
+  
+  self->ui.screen = zori_new_screen(-1, self->display);
+  
+  if(!ZORI_ID_OK_P(self->ui.screen)) {
+    return state_errmsg_(self, "Could not init GUI screen.\n");
+  }
+  
+  self->ui.main_page = zori_new_page(-1, self->ui.screen);
+  LOG_NOTE("Main page: %d\n", self->ui.main_page);
+  box = rebox_make(280, 80, 140, 240);  
+  self->ui.main_menu = zori_new_menu(-1, main_page, &box); 
+  
+  LOG_NOTE("Main menu: %d\n", self->ui.main_menu);
+  
+  
+  {
+    Rebox box = rebox_make(300, 100, 100, 60);
+    self->ui.continue_button = zori_new_button(-1, main_menu, &box, "Continue");
+    LOG_NOTE("Button: %d\n", self->ui.continue_button);
+  }
+  {
+    Rebox box = rebox_make(300, 200, 100, 60);
+    self->ui.new_button = zori_new_button(-1, main_menu, &box, "New");
+    LOG_NOTE("Button: %d\n", self->ui.new_button);
+  }
+  
+}
+  
+
+  
+  INTRO_TEXT2 = 
+
+  def make_sub_menu(parent, x, y, w, h, bh)
+    sub_menu = parent.make_menu(x, y, w, h, nil)
+    sub_menu.make_button(x, y + 20, w - 20, bh, "Sub choice 1") do
+      puts "choice 1"
+    end
+    sub_menu.make_button(x, y + 30 + bh, w - 20, bh, "Sub choice 2") do
+      puts "choice 2"
+    end
+    sub_menu.make_button(x, y + 40 + 2* bh, w - 20, bh, "Sub choice 3") do
+      puts "choice 3"
+    end
+    sub_menu.fit_children
+    sub_menu.hide
+    return sub_menu
+  end
+
+  def do_main_menu
+    main_music    = Music.load(:main, '/music/nethis-the_writer.ogg')
+    $lote         = nil
+    $lobe         = nil
+    if PLAY_MUSIC
+      res = main_music.play!
+    end
+    # res = nil
+    # $main_menu = MainMenu.new
+    # $main_menu.active = true
+    Zori.make_page(:default) do |m|
+      State.talk_box            = m.make_longtext(10, 310, 620, 160, "Testing 1 2 3")
+      State.talk_box.graph.each { |g| g.font = Eruta::Zori.font.id }
+      State.talk_box.delay      = 0.1
+      State.talk_box.page_lines = 5
+      State.talk_box.page       = 0
+      State.talk_box.hide
+    end
+
+    
+    Zori.make_page(:main_menu) do |m|
+      if MAIN_BACKGROUND 
+        main_back    = Bitmap.load(:main_back,
+                      '/image/background/eruta_mainmenu.png')
+        p main_back, main_back.width, main_back.height, main_back.name, main_back.id
+                      
+        main_back    = Bitmap.load(:main_back,
+                      '/image/background/eruta_mainmenu.png')
+        p main_back, main_back.width, main_back.height, main_back.name, main_back.id
+        p Bitmap[:main_back]
+        p Store[:bitmap_main_back]
+        m.graph_image(0, 0, main_back.id)
+      end
+
+      main_menu     = m.make_menu(250, 190, 120, 440, nil)
+      ma            = main_menu
+
+      main_button_1 = ma.make_button(260, 200, 100, 30, "Continue")
+      sub_menu = make_sub_menu(main_button_1, 50, 190, 120, 440, 30)
+      sub_menu.hide
+      
+      main_button_2 = ma.make_button(260, 240, 100, 30, "New") do
+        do_start_test_map
+        Zori.go(:default)
+      end
+      
+      main_button_3 = ma.make_button(260, 280, 100, 30, "Settings") do
+        Zori.go(:settings)
+      end
+      
+      main_button_4 = ma.make_button(260, 320, 100, 30, "Instructions")
+      main_button_5 = ma.make_button(260, 360, 100, 30, "µ£éè")
+      main_button_5 << sub_menu
+      
+      main_button_5= ma.make_button(260, 400, 100, 30, "Quit") do
+        Eruta.quit
+      end
+      
+      main_menu.fit_children
+    end
+
+    Zori.make_page(:settings) do |se|
+        lote2          = se.make_longtext(100, 10, 160, 100, INTRO_TEXT)
+        lote2.delay    = 1
+        lote2.page     = 0
+        lote2.page_lines = 3
+        # $lote2.line_stop = 999990
+        
+        settings_menu  = se.make_menu(480, 380, 120, 440, nil)
+        sm             = settings_menu
+        settings_ok_button = sm.make_button(500, 300, 100, 30, "Font 1") do
+           if lote2
+              lote2.graph.each { |g| g.font = 0 }
+           end
+        end
+        settings_ok_button = sm.make_button(500, 350, 100, 30, "Font 2") do
+           if lote2
+              lote2.graph.each { |g| g.font = Eruta::Zori.font.id }
+           end
+           lote2.text = INTRO_TEXT2
+        end
+        settings_ok_button = sm.make_button(500, 400, 100, 30, "OK") do
+           Zori.go(:main_menu)
+           if lote
+              lote.close
+              lote = nil
+           end
+        end
+        sm.fit_children
+    end
+
+    Zori[:main_menu].hide
+    Zori[:settings].hide
+
+    Zori.go(:main_menu)
+  end
+#endif
 
 

+ 15 - 1
src/zori/zori.c

@@ -309,7 +309,8 @@ zori_id zori_start(struct zori_style * default_style) {
   
  
   the_default_style->text.font      = al_create_builtin_font();  
-  the_default_style->text.color     = al_color_name("white");  
+  the_default_style->text.color     = al_color_name("white");
+  the_default_style->text.font_flags= ALLEGRO_ALIGN_LEFT;
   the_default_style->border.color   = al_color_name("white");  
   the_default_style->back.color     = al_color_name("green");
   the_default_style->fore.color     = al_color_name("white");  
@@ -421,6 +422,19 @@ zori_id zori_set_padding(zori_id id, int size) {
 }
 
 
+zori_font * zori_widget_text_font(struct zori_widget * widget) {  
+  if ((!widget)) {
+    return the_default_style->text.font;
+  }
+  return  widget->style.text.font;
+}
+
+zori_font * zori_text_font(zori_id id) {  
+  struct zori_widget * widget = zori_get_widget(id);
+  return zori_widget_text_font(widget);
+}
+
+
 
 struct zori_widget * zori_widget_init
   ( struct zori_widget * widget, 

+ 499 - 0
src/zori/zori_longtext.c

@@ -1,7 +1,506 @@
 
+#include "monolog.h"
+#include "zori.h"
 #include "zori_longtext.h"
 
 
+#define ZORI_LONGTEXT_LINE_POS_MAX 99999
+
+
+struct zori_longtext * zori_widget_to_longtext(struct zori_widget * widget) {
+  return ZORI_CONTAINER_OF(widget, struct zori_longtext, widget);
+}
+
+
+struct zori_longtext *
+zori_longtext_set(struct zori_longtext * longtext, const zori_string * text) {
+  if (longtext) {
+    if (longtext->text) {
+      ustr_free(longtext->text);
+      longtext->text = NULL;
+    }
+    if (text) { 
+      longtext->text = ustr_dup(text);
+      if (!longtext->text) {
+        LOG_ERROR("Out of memory in longtext setup.");
+      }
+    }
+  }
+  return longtext;
+} 
+
+struct zori_longtext *
+zori_longtext_set_cstr(struct zori_longtext * longtext, const char * cstr) {
+  const USTR * ustr; 
+  USTR_INFO info;
+  ustr = ustr_refcstr(&info, cstr);
+  return zori_longtext_set(longtext, ustr);
+}
+
+struct zori_longtext *
+zori_longtext_init_cstr(struct zori_longtext * longtext, const char * cstr) {
+  longtext->text = NULL;
+  return zori_longtext_set_cstr(longtext, cstr);
+}
+
+void zori_longtext_done(struct zori_longtext * longtext) {
+  zori_longtext_set(longtext, NULL);
+}
+
+
+/** Gets the current text page for a longtext. */
+int zori_longtext_page(struct zori_longtext * longtext) {
+  if (!longtext) return -2;
+  return longtext->line_start / longtext->page_lines;
+}
+
+
+/** Gets the last page number for a longtext or negative on error. */
+int zori_longtext_last_page(struct zori_longtext * longtext) {
+  if (!longtext) return -2;
+  return longtext->line_max / longtext->page_lines;
+}
+
+/** Advances long text to the given page. Automatically unpauses as well. */
+int zori_longtext_page_(struct zori_longtext * longtext, int page) {
+  if (!longtext) return -2;
+  if (page < 0) return -3;  
+  int line  = page * longtext->page_lines;
+  /* Check for page overflow. */
+  if (line >= longtext->line_max) {
+    return -5;    
+  }
+   
+  longtext->paused     = false;
+  longtext->line_start = line;
+
+  /* Negative delay is instant display. */
+  if (longtext->delay < 0) {  
+    longtext->line_stop  = longtext->line_start + longtext->page_lines;
+    longtext->line_pos   = ZORI_LONGTEXT_LINE_POS_MAX;
+  } else {
+    longtext->line_stop  = longtext->line_start + 1;
+    longtext->line_pos   = 0;
+  }  
+  return page;  
+}
+
+/* Returns TRUE if the longtext node is at the end of the text to display,
+ * false if not (more text to display) */
+int zori_longtext_at_end(struct zori_longtext * longtext) {
+  if (!longtext) return FALSE;
+  return  ((longtext->line_stop >= longtext->line_max) &&
+           (longtext->line_pos >= ZORI_LONGTEXT_LINE_POS_MAX));
+}
+
+
+
+/* This function is the helper callback that implements
+ * updating scrolled/partial text for Longtexts. 
+ */
+static bool 
+zori_longtext_update_custom_partial_text(int line_num, const char *line, int size, void * extra) {
+  struct zori_longtext * longtext = extra;
+  longtext->line_max = line_num;
+
+  /* Don't draw lines before start but keep on drawing (for later lines) */
+  if (line_num < longtext->line_start) return true;
+  /* Don't draw lines after stop, but keep calculating to get correct amount of lines */
+  if (line_num >= longtext->line_stop) return true;
+  /* Reveal letter by letter on last line */
+  if (line_num == (longtext->line_stop - 1)) {
+    /* Advance the position automatically if not paused. */
+    if (!longtext->paused) {
+      longtext->line_pos++;
+    }
+    /* Reached eol, advance to next line. */
+    if (longtext->line_pos >= size) {
+      /* Is if the text window is full, pause, otherwise show the next line. */
+      if ((longtext->line_stop - longtext->line_start) >= longtext->page_lines) {
+        longtext->paused = true;
+      } else { 
+        longtext->line_stop++;
+        longtext->line_pos = 0;
+      }
+    } 
+  }
+  (void) line;
+  return true;
+}
+
+/* Updates the longtext, enables scrolling and per character display. */
+void zori_longtext_update_longtext(struct zori_longtext * longtext, double dt) {
+  float width = longtext->widget.inner.size.x;
+  zori_text * text = longtext->text;
+  
+  if (!text) {
+    LOG_WARNING("Attempted to update a NULL longtext.");
+    return;
+  }
+
+  /* Delay advance of characters somewhat. */
+  longtext->delay_total += dt;
+  if (longtext->delay_total < longtext->delay) return;
+  longtext->delay_total = 0;
+  
+  longtext->line_max = 0;
+  al_do_multiline_text(zori_widget_font(&longtext->widget), width,
+      (const char *) text, zori_longtext_update_custom_partial_text, longtext);
+  longtext->line_max++;
+  
+  /* pause if the last line is reached, and prevent overflow. */
+  if (longtext->line_stop > longtext->line_max) {
+    longtext->paused    = true;
+    longtext->line_pos  = ZORI_LONGTEXT_LINE_POS_MAX;
+    longtext->line_stop = longtext->line_max;
+  }
+
+}
+
+/* Returns TRUE if the longtext node is at the end of the text to display,
+ * false if not (more text to display) */
+int zori_longtext_longtext_at_end(struct zori_longtext * longtext) {
+  return  ((longtext->line_stop >= longtext->line_max) &&
+           (longtext->line_pos >= ZORI_LONGTEXT_LINE_POS_MAX));
+}
+
+/* Calculates the x and y position where to draw text, taking alignment and
+ * margin into consideration. */
+static BeVec
+zori_longtext_calculate_text_position(struct zori_longtext * longtext) {
+  BeVec result;
+  int flags = longtext->widget.style.text.font_flags;
+  result.x  = longtext->widget.inner.at.x;
+  result.y  = longtext->widget.inner.at.y;
+
+  if (flags & ALLEGRO_ALIGN_CENTER) {
+    result.x += longtext->widget.inner.size.x / 2;
+  } else if (flags & ALLEGRO_ALIGN_RIGHT) {
+    result.x += longtext->widget.inner.size.x;
+  }
+  return result;
+}
+
+void zori_longtext_draw_text(struct zori_longtext * longtext) {
+  zori_font * font;
+  int flags;
+  BeVec pos = zori_longtext_calculate_text_position(longtext);
+  font      = zori_widget_font(&longtext->widget);
+  flags     = longtext->widget.style.text.font_flags | ALLEGRO_ALIGN_INTEGER;
+  zori_color shadow = longtext->widget.style.back.color;
+  zori_color fore   = longtext->widget.style.text.color;
+  
+  /* Draw the text twice, once offset in bg color to produce a shadow, 
+   and once normally with foreground color. */
+  al_draw_ustr(font, shadow, pos.x + 1, pos.y + 1,
+    flags, longtext->text);
+  al_draw_ustr(font, fore, pos.x, pos.y,
+    flags, longtext->text);
+}
+
+
+/* This function is the helper callback that implements the actual drawing
+ * for zori_longtext_draw_partial_text.
+ */
+static bool zori_longtext_draw_custom_partial_text(int line_num, const char *line,
+  int size, void *extra) {
+  float x, y;
+  struct zori_longtext * longtext = extra;  
+  ALLEGRO_USTR_INFO info;
+  int real_size, flags;
+  double width;
+  ALLEGRO_FONT * font;
+  BeVec pos;
+  
+  font      = zori_widget_font(&longtext->widget);
+  flags     = longtext->widget.style.text.font_flags | ALLEGRO_ALIGN_INTEGER;
+  pos       = zori_longtext_calculate_text_position(longtext);
+ 
+
+  
+  /* Don't draw lines before start but keep on drawing (for later lines) */
+  if (line_num < longtext->line_start) return true;
+  /* Don't draw lines after stop, drawing is done */
+  if (line_num >= longtext->line_stop) return false;
+  real_size = size;
+  /* calculate position */
+  x  = pos.x;
+  y  = pos.y + (longtext->line_height * (line_num - longtext->line_start));
+
+  /* Reveal letter by letter on last line */
+  if (line_num == (longtext->line_stop - 1)) { 
+    real_size = (longtext->line_pos < size) ? longtext->line_pos : size;
+
+    /* Draw continuation marker if paused. XXX... this needs to be done 
+     * better... Maybe a bitmap member or so? */
+    if (longtext->paused) {
+      float x1, y1, x2, y2, x3, y3;
+      y2 = y  + 8;
+      x2 = x + longtext->widget.inner.size.x;
+      x1 = x2 - 8;
+      y1 = y2;
+      x3 = x2 - 4;
+      y3 = y2 + 8;
+      al_draw_filled_triangle(x1, y1, x2, y2, x3, y3, longtext->widget.style.text.color);
+    }
+  }
+  
+  zori_color shadow = longtext->widget.style.back.color;
+  zori_color fore   = longtext->widget.style.text.color;
+  
+  al_draw_ustr(font, shadow,
+      x + 1, y + 1, flags, al_ref_buffer(&info, line, real_size));
+  al_draw_ustr(font, fore,
+      x, y, flags, al_ref_buffer(&info, line, real_size));
+                
+  return true;
+}
+
+
+/* Allows to draw a multi line text partially, from line_start up to
+ * line_stop. Draws scrolling text from a prefilled struct. */ 
+void zori_longtext_draw_partial_text(struct zori_longtext * longtext) {
+  float width = longtext->widget.inner.size.x;
+  zori_font * font = zori_widget_font(&longtext->widget);
+  
+  if (!longtext->text) {
+    LOG_WARNING("Trying to draw a NULL longtext.");
+    return;
+  }
+  
+  const char * text = ustr_c(longtext->text);
+  
+  /* It's a bit of a hack that this ends up here... */
+  if (longtext->line_height < 1.0) {
+    longtext->line_height = al_get_font_line_height(font);
+  }
+  
+  al_do_multiline_text(font, width,
+      (const char *) text, zori_longtext_draw_custom_partial_text, longtext);  
+}
+
+
+void zori_draw_longtext(struct zori_longtext * longtext) {
+  zori_longtext_draw_partial_text(longtext);
+}
+
+
+/** Sets the last line to display for a long text */
+zori_id zori_set_line_stop(zori_id index, int stop) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL; 
+  longtext->line_stop = stop;
+  return index;
+}
+
+/** Sets the first line to display for a long text */
+zori_id zori_line_start_(zori_id index, int start) {
+struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL; 
+  longtext->line_start = start;
+  return index;
+}
+
+/** Sets display delay between individual characters for a long text */
+int zori_delay_(zori_id index, double delay) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL; 
+  longtext->delay = delay;
+  return index;
+}
+
+
+
+/** Gets the last line to display for a long text  or negative on error*/
+int zori_line_stop(int index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return longtext->line_stop;
+}
+
+/** Gets the first line to display for a long text */
+int zori_line_start(int index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return longtext->line_start;
+}
+
+/** Gets display delay between individual characters  for a long text */
+double zori_delay(int index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return longtext->delay;
+}
+
+
+/** Sets amount of shown lines for a long text */
+int zori_page_lines_(zori_id index, int lines) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  longtext->page_lines = lines;
+  return index;
+}
+
+/** Gets amount of lines for a "page" of long text */
+int zori_page_lines(int index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return longtext->page_lines;
+}
+
+/** Sets paused state of long text */
+int zori_paused_(zori_id index, int paused) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  longtext->paused = paused;
+  return index;
+}
+
+/** Gets paused state of long text */
+int zori_paused(zori_id index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return 0;
+  return longtext->paused;
+}
+
+/** Gets the current text page for a longtext. */
+int zori_page(zori_id index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return zori_longtext_page(longtext);
+}
+
+/** Gets the number of the last text page for a longtext. */
+int zori_last_page(zori_id index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;
+  return zori_longtext_last_page(longtext);
+}
+
+/** Returns nonzero if the long text is at the end of it's display. */
+int zori_at_end(zori_id index) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  if (!longtext) return ZORI_ID_EINVAL;  
+  return zori_longtext_at_end(longtext);
+}
+
+/** Advances long text to the given page. Automatically unpauses as well. */
+int zori_page_(zori_id index, int page) {
+  struct zori_widget * widget = zori_get_widget(index);
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+  return zori_longtext_page_(longtext, page);
+}
+
+/** Advances long text to the next page. Automatically unpauses as well. */
+int zori_next_page(zori_id index) {
+  int page = zori_page(index);
+  if (page < 0) return page;
+  return zori_page_(index, page + 1); 
+}
+
+/** Advances long text to the previous page. Automatically unpauses as well. */
+int zori_previous_page(int index) {
+  int page = zori_page(index);
+  if (page < 0) return page;
+  return zori_page_(index, page - 1); 
+}
+
+
+
+
+/** Handles a mouse axis event and pass it on. */
+int zori_longtext_on_mouse_axes(union zori_event * event) { 
+    struct zori_widget * widget     = event->any.widget;
+    struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+    float x = event->sys.ev->mouse.x;
+    float y = event->sys.ev->mouse.y;
+    if (zori_xy_inside_widget_p(widget, x, y)) {
+      zori_widget_hover_(widget, TRUE);
+    } else {
+      zori_widget_hover_(widget, FALSE);
+    }
+    
+    return ZORI_HANDLE_PASS;
+}
+
+/** Handles a mouse click event and pass it on.  */
+int zori_longtext_on_mouse_click(union zori_event * event) { 
+    struct zori_widget * widget = event->any.widget;
+    struct zori_longtext * longtext = zori_widget_to_longtext(widget);
+    float x = event->sys.ev->mouse.x;
+    float y = event->sys.ev->mouse.y;
+    if (zori_xy_inside_widget_p(widget, x, y)) {
+      widget->result = widget->id;
+      LOG_NOTE("Longtext %d clicked: %d, result %d.\n", widget->id, widget->result);
+      return zori_widget_raise_action_event(widget);
+    }
+    return ZORI_HANDLE_PASS;
+}
+
+
+int zori_longtext_on_draw(union zori_event * event) {
+  struct zori_widget * widget = event->any.widget;
+  struct zori_longtext * longtext = zori_widget_to_longtext(widget);  
+  zori_draw_longtext(longtext);
+  return ZORI_HANDLE_PASS;
+}
+
+
+struct zori_handler zori_longtext_handlers[] = {
+  { ZORI_SYSTEM_EVENT_MOUSE_BUTTON_DOWN , zori_longtext_on_mouse_click   , NULL },
+  { ZORI_SYSTEM_EVENT_MOUSE_AXES        , zori_longtext_on_mouse_axes    , NULL },
+  { ZORI_EVENT_DRAW                     , zori_longtext_on_draw          , NULL },
+  { -1, NULL, NULL }
+};
+
+
+struct zori_longtext * 
+zori_longtext_init(struct zori_longtext * longtext, const char * text) {
+  if (longtext) {
+    zori_longtext_set_cstr(longtext, text);
+  }
+  return longtext;
+}
+
+struct zori_longtext * zori_longtext_new(zori_id id, zori_id parent_id, 
+  zori_box * box, const char * text) {
+  struct zori_longtext * longtext = NULL;
+  longtext = calloc(1, sizeof(*longtext));
+  if (!longtext) return NULL;
+  zori_widget_initall(&longtext->widget, ZORI_WIDGET_TYPE_LONGTEXT, id, zori_get_widget(parent_id), 
+                      box, NULL, ZORI_ARRAY_AND_AMOUNT(zori_longtext_handlers)); 
+  if (!zori_longtext_init(longtext, text)) {
+    free(longtext);
+    longtext = NULL;
+  }
+  zori_widget_hover_(&longtext->widget, 0);
+  
+  return longtext;
+}
+
+
+zori_id zori_new_longtext(zori_id id, zori_id parent, zori_box * box, const char * text) {
+  struct zori_longtext * longtext = zori_longtext_new(id, parent, box, text);
+  if (!longtext) return ZORI_ID_ENOMEM;
+  return longtext->widget.id;
+}
+
+
+
+
 
 
 

+ 9 - 6
src/zori/zori_screen.c

@@ -61,13 +61,16 @@ int zori_screen_on_overdraw(union zori_event * event) {
   return ZORI_HANDLE_PASS;
 }
 
-
+/* Handlers must beset up for many events on screen to funnel them all into 
+ * the active page. */
 struct zori_handler zori_screen_handlers[] = {
-  { ZORI_SYSTEM_EVENT_KEY_DOWN  , zori_screen_on_sysevent       , NULL }, 
-  { ZORI_SYSTEM_EVENT_KEY_UP    , zori_screen_on_sysevent       , NULL },
-  { ZORI_SYSTEM_EVENT_KEY_CHAR  , zori_screen_on_sysevent       , NULL },
-  { ZORI_SYSTEM_EVENT_MOUSE_AXES, zori_screen_on_mouse_axes     , NULL },
-  { ZORI_EVENT_OVERDRAW         , zori_screen_on_overdraw       , NULL },
+  { ZORI_SYSTEM_EVENT_KEY_DOWN          , zori_screen_on_sysevent       , NULL }, 
+  { ZORI_SYSTEM_EVENT_KEY_UP            , zori_screen_on_sysevent       , NULL },
+  { ZORI_SYSTEM_EVENT_KEY_CHAR          , zori_screen_on_sysevent       , NULL },
+  { ZORI_SYSTEM_EVENT_MOUSE_BUTTON_DOWN , zori_screen_on_sysevent       , NULL },
+  { ZORI_SYSTEM_EVENT_MOUSE_BUTTON_UP   , zori_screen_on_sysevent       , NULL },
+  { ZORI_SYSTEM_EVENT_MOUSE_AXES        , zori_screen_on_mouse_axes     , NULL },
+  { ZORI_EVENT_OVERDRAW                 , zori_screen_on_overdraw       , NULL },
   { -1, NULL, NULL }
 };
 

+ 1 - 12
test/test_ui.c

@@ -4,23 +4,12 @@
 #include "si_test.h"
 #include "eruta.h"
 #include "image.h"
+#include "zori/zori.h"
 #include "ui.h"
 
 #include <stdarg.h>
 
-int test_va(int last, ...) {
-  int res = 0;  double d = 0.0; char * str = NULL;
-  va_list args;
-  va_start(args, last);
-  va_list_scan(args, "ifs", &res, &d, &str);
-  va_end(args);
-  printf("%d %f %s\n", res, d, str);
-  return res;
-}
-
-
 TEST_FUNC(ui) {
-  TEST_INTEQ(1375, test_va(0, 1375, 12.34, "Aha!"));
   TEST_DONE();
 }