GTKmm 練習筆記(三)Drawing Area實作動畫鬧鐘(cairo 向量繪圖)

来源:互联网 发布:unity3d开发地方麻将 编辑:程序博客网 时间:2024/05/23 23:08

這次的筆記一樣是來自GTKmm GitHub上的範例 範例連結

內容是利用GTKmm的Drawing Area來繪製能依據現在時間做變化的動畫鬧鐘。

我將會記下自己對源碼的理解以及函數的解析,以及實作過程。

如果有不知道如何架設GTKmm的朋友可以先看我之前的文章,GTKmm環境架設。


範例截圖(一)


範例截圖(二)

在進入源碼解說前,先來介紹一下GTKmm官網對於Drawing Area控件的基礎使用說明。來源連結

先介紹一下會使的我們接下來閱讀實現源碼時會輕鬆許多。


在GTKmm中,繪圖的功能主要藉由另一個開源的向量繪圖庫Cairo來實現(念法近似"開羅"),

所以學習DrawingArea這個控件的方法,其實就是學習Cairo繪圖庫的使用方式。


而基本的繪圖方式為:

(一)在畫布上先定義出想要繪製出來的圖線路徑(Path),注意,但定義完成後這個路徑是看不見的。

(二)如果要將其在畫布上顯現出來,則要利用路徑描繪(Stroking)或填滿他們(filling)的函數。

(三)堆疊的使用,一個Context代表一個圖形狀態,Cairo內建一個堆疊,使你可以使用Context.save()將現在狀態放入堆疊中,也可以

使用Context.restore()將堆疊最上面的的狀態覆寫回來;我們可以透過這個功能來實現圖片的儲存及回復,由於這是一個堆疊,所以可以反覆嵌套使用。


在開始繪圖之前,我們必須先創造Cairo::Context 這個類別物件,這個物件裡將包含許多參數來告訴畫布我們將如何將圖形顯現出來。

在GTKmm中我們將利用Gdk::Window::create_cairo_contex()這個函數來創Cairo::Contex,這個函數將返回

Cairo::RefPtr<cairo::contex>這個物件。


以下是一個簡單的利用DrawingArea控件及Cairo庫的使用範例,我們可以很輕易的在源碼中了解他們之間的關係。

Gtk::DrawingArea myArea;  //創建DrawingArea控件Cairo::RefPtr<Cairo::Context> myContext = myArea.get_window()->create_cairo_context();//創建Cairo::ContextmyContext->set_source_rgb(1.0, 0.0, 0.0);//利用Context的繪圖函數將畫布染成紅色

在了解完大致的使用方法後,我們可以開始進入鬧鐘範例源碼閱讀的部分了。

首先來看一下鬧鐘類的架構。

clock.h

#ifndef GTKMM_EXAMPLE_CLOCK_H#define GTKMM_EXAMPLE_CLOCK_H#include <gtkmm/drawingarea.h>//在使用DrawingArea之前必須先將其引入class Clock : public Gtk::DrawingArea//這個類直接繼承DrawingArea{public:  Clock();  virtual ~Clock();protected:  //Override default signal handler://翻譯:覆寫原有在DrawingArea的on_draw()方法  bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;  bool on_timeout();  double m_radius;  double m_line_width;};#endif // GTKMM_EXAMPLE_CLOCK_H

接下來是clock.cpp

#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 );}Clock::~Clock(){}bool Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr){  Gtk::Allocation allocation = get_allocation();//獲取關於當前控件的參數                               const int width = allocation.get_width();//獲取寬度  const int height = allocation.get_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();//將最上層參數pop回來  cr->arc(0, 0, m_radius, 0, 2 * M_PI);//將一個弧形放入路徑中,參數(圓心.x,圓心.y,畫多少弧度,起點,終點(弧度角度))//在這裡指的是以視窗中心為圓心,畫一個圓。arc               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++)//畫12個刻度  {    double inset = 0.05;//內圈與外圈半徑差    cr->save();//保存當前路徑    cr->set_line_cap(Cairo::LINE_CAP_ROUND);//將當前的畫筆參數的線條樣式更改為圓頭線條    if(i % 3 != 0)//從0~11 假設不是0,3,6,9則將其畫筆長度縮小    {      inset *= 0.8;//將內外圈差縮小0.8      cr->set_line_width(0.03);//將粗細設為0.03,比0,3,6,9點的刻度更細。    }//以下開始畫刻度    cr->move_to(      (m_radius - inset) * cos (i * M_PI / 6),//線條原點.x設為 半徑*cos(弧度))的值(不了解的可以自行百度一下斜邊長與座                                                                   //標關係)      (m_radius - inset) * sin (i * M_PI / 6));//線條原點.y設為 半徑*sin(弧度)的值    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;//2PI弧度是一個圓呈上角度分割  double hours = timeinfo->tm_hour * M_PI / 6;  double seconds= timeinfo->tm_sec * M_PI / 30;  cr->save();//保存當前路徑(空白)  cr->set_line_cap(Cairo::LINE_CAP_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();  return true;}bool Clock::on_timeout(){    // force our program to redraw the entire clock.//翻譯:每當時間一到,發出通知使整個窗口重繪(註記:重要)    auto win = get_window();    if (win)    {        Gdk::Rectangle r(0, 0, get_allocation().get_width(),                get_allocation().get_height());        win->invalidate_rect(r, false);    }    return true;}

最後是main.cpp,因為一開始就把整個主窗口繼承自DrawingArea了。因此直接實現他就行。

#include "clock.h"#include <gtkmm/application.h>#include <gtkmm/window.h>int main(int argc, char** argv){   auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");   Gtk::Window win;   win.set_title("Cairomm Clock");   Clock c;   win.add(c);   c.show();   return app->run(win);}

打完收工!




0 0