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

    xiaoxiao2021-12-01  26

    這次的筆記一樣是來自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::Context myContext->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); }

    打完收工!

    
    转载请注明原文地址: https://ju.6miu.com/read-679395.html

    最新回复(0)