Ukázková aplikace: Kreslení hodin pomocí knihovny Cairo

Tímto jsme pokryli všechny základy o kreslení pomocí Cairo. Takže to zkusme dát vše dohromady a vytvořit jednoduchou aplikaci, která bude dělat něco reálného. Následující příklad používá Cairo k vytvoření vlastního widgetu s hodinami Clock. Hodiny mají sekundovou, minutovou a hodinovou ručičku a aktualizují se po sekundě.

Source Code

File: clock.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_CLOCK_H
#define GTKMM_EXAMPLE_CLOCK_H

#include <gtkmm/drawingarea.h>

class Clock : public Gtk::DrawingArea
{
public:
  Clock();
  virtual ~Clock();

protected:
  void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

  bool on_timeout();

  double m_radius;
  double m_line_width;
};

#endif // GTKMM_EXAMPLE_CLOCK_H

File: main.cc (For use with gtkmm 4)

#include "clock.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();

protected:
  Clock m_clock;
};

ExampleWindow::ExampleWindow()
{
  set_title("Cairomm Clock");
  set_child(m_clock);
}

int main(int argc, char** argv)
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: clock.cc (For use with gtkmm 4)

#include <ctime>
#include <cmath>
#include <cairomm/context.h>
#include <glibmm/main.h>
#include "clock.h"

Clock::Clock()
: m_radius(0.42), m_line_width(0.05)
{
  Glib::signal_timeout().connect( sigc::mem_fun(*this, &Clock::on_timeout), 1000 );
  set_draw_func(sigc::mem_fun(*this, &Clock::on_draw));
}

Clock::~Clock()
{
}

void Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
  // scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e.
  // the center of the window
  cr->scale(width, height);
  cr->translate(0.5, 0.5);
  cr->set_line_width(m_line_width);

  cr->save();
  cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
  cr->paint();
  cr->restore();
  cr->arc(0, 0, m_radius, 0, 2 * M_PI);
  cr->save();
  cr->set_source_rgba(1.0, 1.0, 1.0, 0.8);
  cr->fill_preserve();
  cr->restore();
  cr->stroke_preserve();
  cr->clip();

  //clock ticks
  for (int i = 0; i < 12; i++)
  {
    double inset = 0.05;

    cr->save();
    cr->set_line_cap(Cairo::Context::LineCap::ROUND);

    if(i % 3 != 0)
    {
      inset *= 0.8;
      cr->set_line_width(0.03);
    }

    cr->move_to(
      (m_radius - inset) * cos (i * M_PI / 6),
      (m_radius - inset) * sin (i * M_PI / 6));
    cr->line_to (
      m_radius * cos (i * M_PI / 6),
      m_radius * sin (i * M_PI / 6));
    cr->stroke();
    cr->restore(); /* stack-pen-size */
  }

  // store the current time
  time_t rawtime;
  time(&rawtime);
  struct tm * timeinfo = localtime (&rawtime);

  // compute the angles of the indicators of our clock
  double minutes = timeinfo->tm_min * M_PI / 30;
  double hours = timeinfo->tm_hour * M_PI / 6;
  double seconds= timeinfo->tm_sec * M_PI / 30;

  cr->save();
  cr->set_line_cap(Cairo::Context::LineCap::ROUND);

  // draw the seconds hand
  cr->save();
  cr->set_line_width(m_line_width / 3);
  cr->set_source_rgba(0.7, 0.7, 0.7, 0.8); // gray
  cr->move_to(0, 0);
  cr->line_to(sin(seconds) * (m_radius * 0.9),
    -cos(seconds) * (m_radius * 0.9));
  cr->stroke();
  cr->restore();

  // draw the minutes hand
  cr->set_source_rgba(0.117, 0.337, 0.612, 0.9);   // blue
  cr->move_to(0, 0);
  cr->line_to(sin(minutes + seconds / 60) * (m_radius * 0.8),
    -cos(minutes + seconds / 60) * (m_radius * 0.8));
  cr->stroke();

  // draw the hours hand
  cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
  cr->move_to(0, 0);
  cr->line_to(sin(hours + minutes / 12.0) * (m_radius * 0.5),
    -cos(hours + minutes / 12.0) * (m_radius * 0.5));
  cr->stroke();
  cr->restore();

  // draw a little dot in the middle
  cr->arc(0, 0, m_line_width / 3.0, 0, 2 * M_PI);
  cr->fill();
}

bool Clock::on_timeout()
{
  // force our program to redraw the entire clock.
  queue_draw();
  return true;
}

As before, almost all of the interesting stuff is done in the draw function on_draw(). Before we dig into the draw function, notice that the constructor for the Clock widget connects a handler function on_timeout() to a timer with a timeout period of 1000 milliseconds (1 second). This means that on_timeout() will get called once per second. The sole responsibility of this function is to invalidate the window so that gtkmm will be forced to redraw it.

Nyní se zaměřme na kód, který provádí skutečné vykreslení. První část funkce on_draw() by vám měla být dobře známá. Tento příklad znovu mění měřítko systému souřadnic na čtvercovou jednotku, takže bude jednodušší vykreslit hodiny jako procentní část velikosti okna a při změně velikosti okna dojde k automatickému přizpůsobení velikosti hodin. Navíc je systém souřadnic posunut vpřed a dolů, takže souřadnice (0, 0) je přímo uprostřed okna.

Funkce Cairo::Context::paint() je zde použita k nastavení barvy pozadí okna. Tato funkce nepřebírá žádný argument a vyplní aktuální povrch (nebo oříznutou část povrchu) právě aktivní zdrojovou barvou. Po nastavení barvy pozadí okna nakreslíme kružnici tvořící obrys hodin a následně tu stejnou cestu ořízneme, abychom si zajistili, že naše následující čáry nepůjdou mimo hodiny.

Po nakreslení obrysu projdeme po ciferníku a nakreslíme značky pro každou hodinu, s výraznějšími značkami pro 3, 6, 9 a 12 hodin. Nyní máme vše připravené k implementaci funkcionality udržování času na hodinách, což představuje získání aktuální hodnoty pro hodiny, minuty a sekundy a vykreslení ručiček v příslušném úhlu.