GUI实现原理

只用到了类的基本语法,初次接触就去看“菜鸟教程”的C++的类的部分
基于ARMGCC
使用FreeRTOS,内存管理为heap4或heap5,可防止出现内存碎片
使用1bit像素模式,其它的同理

extern "C"
{
    extern void *pvPortMalloc(size_t xWantedSize);
    extern void vPortFree(void *pv);
}

//重载new和delete
void *operator new(size_t size)
{
    return pvPortMalloc(size);
}

void operator delete(void *pointer)
{
    vPortFree(pointer);
}

void (*fill_srcee_)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
//坐标系是左上角为原点,x向右递增,y向下递增!
const uint16_t main_window_width_ = 128;
const uint16_t main_window_height_ = 64; //所有的坐标系将以这个为准 //坐标以左上角为原点(0,0),x向右递增,y向下递增
const uint8_t origin_ = 0;                //仅用于代替坐标原点的0

window *window::main_window_ = nullptr, *window::desktop_window_ = nullptr;
uint8_t window::creat_main_window_flag_ = false;

class window
{
public: //为了简化流程,这里用public属性
    int16_t x_, y_;                                         //窗口的起点(相对于父窗口的)
    uint16_t width_, height_;                               //窗口宽高,单位是像素。>=1
    static window *main_window_, *desktop_window_;          //主窗口地址,桌面窗口地址
    window *previous_ = nullptr, *next_ = nullptr;          //同级窗口之间的联系。也是绘制顺序,previou比next先绘制。选中的窗口会被移动到树的最后面,则该窗口必定覆盖在其它窗口上
    window *first_child_ = nullptr, *last_child_ = nullptr; //第一个和最后一个child地址
    window *parent_ = nullptr;                              //父窗口地址
    static uint8_t creat_main_window_flag_;                 //主窗口是否建立的标志
    void *display_buffer_;                                  //画面缓存。要注意这是void类型的数组,对其元素取值/赋值时要转换类型,并且类型必须要对。为了兼容各种位数的像素,1bit,16bit等

    window(int16_t x = 0, int16_t y = 0, uint16_t width = main_window_width_, uint16_t height = main_window_height_);
    ~window();

    void draw_pixel(uint16_t x, uint16_t y, uint32_t color); //向自己的画面混村里画个像素点
    uint32_t get_pixel(uint16_t x, uint16_t y); //得到指定位置的像素点颜色
    static void draw_main_window_pixel(uint16_t x, uint16_t y, uint32_t color) //向主窗口(帧缓存)里画像素点

    void connect(window *parent); //连接一个窗口,该窗口作为自己的父窗口
    void disconnect(void); //与父窗口断开连接

    int16_t get_x2(void) { return x_ + width_ - 1; }
    int16_t get_y2(void) { return y_ + height_ - 1; }

    static void set_fill_srceen_pt(void (*fill_srcee)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data)); //设置屏幕的绘图函数
    static void updata_all_window(void); //将所有连接到树状图上的窗口的画面更新到主窗口(帧缓存)里
    void updata_my_window(uint16_t display_x, uint16_t display_y, uint16_t display_width, uint16_t display_height, uint16_t x, uint16_t y); //将自己的窗口的画面更新到主窗口(帧缓存)里。由updata_all_window调用
    uint8_t calculate_overlap_range(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t &p_sta_x, uint16_t &p_sta_y, uint16_t &my_sta_x, uint16_t &my_sta_y, uint16_t &my_width, uint16_t &my_height); //计算两个矩形的相交部分的面积和相交部分的起点
};

window::window(int16_t x, int16_t y, uint16_t width, uint16_t height) : x_(x), y_(y), width_(width > 0 ? width : 1), height_(height > 0 ? height : 1)
{
    if (!creat_main_window_flag_)
    {
        creat_main_window_flag_ = true;
        desktop_window_ = this;

        //建立主窗口
        main_window_ = new window;
        connect(main_window_);

        x_ = y_ = origin_;
        width_ = main_window_width_;
        height_ = main_window_height_;
    }

    display_buffer_ = new uint8_t[width_ * (height_ / 8 + (height_ % 8 ? 1 : 0))]; //有余数再加一行
};

window::~window()
{
    delete[] (uint8_t *)display_buffer_;
    if (this == desktop_window_)
        delete main_window_;
}

void window::draw_pixel(uint16_t x, uint16_t y, uint32_t color)
{
    if (x >= width_ || y >= height_)
        return;

    uint16_t addr = y / 8 * width_ + x;
    uint8_t src = ((uint8_t *)display_buffer_)[addr];
    color ? src = (0x01 << (y % 8)) | src : src = (~(0x01 << (y % 8))) & src;
    ((uint8_t *)display_buffer_)[addr] = src;
}

uint32_t window::get_pixel(uint16_t x, uint16_t y)
{
    return (0x01 << (y % 8) & ((uint8_t *)display_buffer_)[y / 8 * width_ + x]) ? 1 : 0;
}

void window::draw_main_window_pixel(uint16_t x, uint16_t y, uint32_t color)
{
    if (x >= main_window_width_ || y >= main_window_height_)
        return;

    uint16_t addr = y / 8 * main_window_width_ + x;
    uint8_t src = ((uint8_t *)(main_window_->display_buffer_))[addr];
    color ? src = (0x01 << (y % 8)) | src : src = (~(0x01 << (y % 8))) & src;
    ((uint8_t *)(main_window_->display_buffer_))[addr] = src;
}

void window::set_fill_srceen_pt(void (*fill_srcee)(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data))
{
    fill_srcee_ = fill_srcee;
}

 

void display(uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
这是display函数的原型,其功能如下
传入绘制区域的起始坐标x,y,绘制区域的像素堆的宽高(大于等于1),绘制数据的第一个字节的地址
绘制的像素数量是width * height,data要被转换为(uint8_t*)类型,并且一个字节包含8个竖着的像素点
对于12864的单色屏
display(0, 0, 128, 64, main_window_->display_buffer_);
{
//简单点,循环128 * 8次,将(uint8_t *)data发出去
}


int main()
{
    //省略系统的初始化

    window::set_fill_srceen_pt(display); //添加绘图函数的指针。不写这个也没关系,显示函数做了判空处理,不会去操作空指针,但是画面就得去内存里面看了
    window a;
    for(;;)
    {
        window::updata_all_window();
    }
}

第一个窗口的创建和绘制就完成了
来梳理整个过程
1、
window a;
创建了一个window类的对象。
执行过程如下:
//使用设定的初值初始化x_、y_、width_、height_的数据

声明:window(int16_t x = 0, int16_t y = 0, uint16_t width = main_window_width_, uint16_t height = main_window_height_);
window::window(int16_t x, int16_t y, uint16_t width, uint16_t height) : x_(x), y_(y), width_(width > 0 ? width : 1), height_(height > 0 ? height : 1)
{
    if (!creat_main_window_flag_) //creat_main_window_flag_是false,!false则等于true,进入
    {
        creat_main_window_flag_ = true; //防止第二次进入
        desktop_window_ = this; //将自己的地址给desktop_window_

        //建立主窗口
        main_window_ = new window; //再创建一个对象,但因为是第二次进入构造函数,所以无法进入这个if语句,将直接执行申请内存的部分,并退出构造函数,然后将地址给main_window_
        connect(main_window_); //连接到main_window_上

        //强制改为设定的初值
        x_ = y_ = origin_;
        width_ = main_window_width_;
        height_ = main_window_height_;
    }
    //申请内存
    display_buffer_ = new uint8_t[width_ * (height_ / 8 + (height_ % 8 ? 1 : 0))]; //有余数再加一行
};

 

connect和disconnect函数是整个系统的基石!
connect和disconnect函数是整个系统的基石!
connect和disconnect函数是整个系统的基石!
动手动笔在纸上画!
动手动笔在纸上画!
动手动笔在纸上画!
懂了这个就会了一半!
懂了这个就会了一半!
懂了这个就会了一半!
连接到从desktop开始的树状图后,遍历相关的函数才会对窗口进行处理

断开与树的连接后,将不再被遍历函数操作
例如虚拟键盘连接到树上后,就会被显示出来,断开连接后就不显示了
窗口的最小化与最大化也是这样,但不代表缓存的资源消失了
mainwin //最终的画面在这里,作为帧缓存。以下称为主窗口
|
desktop //第一个窗口。大小和帧缓存是一样的。从这个窗口开始向下遍历
|   |
w1  w2 //其它窗口

void window::connect(window *parent) //连接父窗口。跟在父窗口的最后一个子窗口右边
{
    //安全第一
    //如果自己是main_window_则退出、如果自己是desktop_window_而要连接的父窗口不是main_window_则退出、连接自己就退出、连接空指针就退出、如果有父窗口则退出
    if (this == main_window_ || (this == desktop_window_ && parent != main_window_) || parent == this || parent == 0 || parent_) 
        return;

    parent_ = parent;                //parent作为自己的父窗口
    previous_ = parent->last_child_; //将父窗口的最后一个child作为自己的上一个窗口

    if (parent_->last_child_)               //如果父窗口有最后一个child
        parent_->last_child_->next_ = this; //把自己作为该child的下一个同级窗口
    
    parent_->last_child_ = this;            //把自己作为父窗口的last_child_

    if (parent_->first_child_ == nullptr) //如果父窗口的第一个child为空
        parent_->first_child_ = this;     //则把自己作为父窗口的第一个child
}

void window::disconnect(void)
{
    if (this == main_window_ || this == desktop_window_ || parent_ == nullptr)
        return;
    //处理其它窗口
    if (previous_)
        previous_->next_ = next_;
    if (next_)
        next_->previous_ = previous_;

    if (parent_->first_child_ == this)
        parent_->first_child_ = next_;

    if (parent_->last_child_ == this)
        parent_->last_child_ = previous_;

    //处理自己的窗口
    previous_ = nullptr, next_ = nullptr;
    parent_ = nullptr;
}

 

明白了以上两个函数再看window::updata_all_window();,知道各个窗口的绘制顺序,怎么计算绘制的区域。

void window::updata_all_window()
{
    if (!desktop_window_)
        return;
    desktop_window_->updata_my_window(origin_, origin_, main_window_width_, main_window_height_, origin_, origin_);

    //向屏幕绘图
    if(fill_srcee_)
        fill_srcee_(origin_, origin_, main_window_width_, main_window_height_, main_window_->display_buffer_);
}

//传入父窗口可显示区域的相对坐标、宽高,自己窗口的绝对位置
void window::updata_my_window(uint16_t display_x, uint16_t display_y, uint16_t display_width, uint16_t display_height, uint16_t x, uint16_t y)
{
    uint16_t p_x, p_y, my_x, my_y, d_width, d_height; //自己窗口的可显示区域相对于父窗口的坐标,在自己窗口里的起始坐标,可显示区域的长宽
    if (calculate_overlap_range(display_x, display_y, display_width, display_height, p_x, p_y, my_x, my_y, d_width, d_height))
    {
        for (uint16_t h = 0; h < d_height; ++h)
        {
            for (uint16_t w = 0; w < d_width; ++w)
                draw_main_window_pixel(x - x_ + p_x + w, y - y_ + p_y + h, get_pixel(w + my_x, h + my_y));
        }

        if (first_child_)
            first_child_->updata_my_window(my_x, my_y, d_width, d_height, first_child_->get_x() + x, first_child_->get_y() + y);
    }

    if (next_)
        next_->updata_my_window(display_x, display_y, display_width, display_height, x - x_ + next_->get_x(), y - y_ + next_->get_y());
}

//计算自己和另一个矩形的相交部分的起点和大小
uint8_t window::calculate_overlap_range(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t &p_sta_x, uint16_t &p_sta_y, uint16_t &my_sta_x, uint16_t &my_sta_y, uint16_t &my_width, uint16_t &my_height)
{
    if (x_ > x + width - 1 || get_x2() < x || y_ > y + height - 1 || get_y2() < y) //自己不在可显示的范围内
        return false;

    uint16_t x_r_min = (x + width - 1) > get_x2() ? get_x2() : (x + width - 1);
    uint16_t x_l_max = x > x_ ? x : x_;
    uint16_t y_t_max = y > y_ ? y : y_;
    uint16_t y_b_min = (y + height - 1) > get_y2() ? get_y2() : (y + height - 1);

    my_width = x_r_min - x_l_max + 1;
    my_height = y_b_min - y_t_max + 1;

    p_sta_x = x_l_max;
    p_sta_y = y_t_max;

    my_sta_x = x_l_max - x_;
    my_sta_y = y_t_max - y_;

    return true;
}

 

这个是判断焦点落在那个窗口上
首先用last_child_,去到整个系统里最后一个窗口,也是最顶端的一个窗口,也是唯一一个不会被覆盖的窗口
焦点的绝对坐标会被转换成窗口的相对坐标,让焦点和“判断焦点是否在自己窗口里”的窗口处于同一坐标系
从最顶端的一个窗口依次向底端窗口发展。一旦确定焦点所在的窗口,将不断的返回,直到退出所有的焦点函数

window *window::get_focus_in_window(int16_t x, int16_t y)
{
    static window *hit;
    hit = nullptr;

    if (x >= x_ && x <= get_x2() && y >= y_ && y <= get_y2()) //只有点在自己窗口内才会去子窗口或者自我判断
    {
        if (last_child_) //去往最后一个子窗口
        {
            hit = last_child_->get_focus_in_window(x - x_, y - y_); //换算成相对于父窗口的坐标
            if (hit != nullptr)                                     //如果焦点在子窗口上则返回获得焦点的窗口,否则进入自我判断
                return hit;
        }

        if (x >= x_ && x <= get_x2() && y >= y_ && y <= get_y2())
        {
            hit = this;
            return hit;
        }
    }

    if (previous_) //去往上一个同级窗口
    {
        hit = previous_->get_focus_in_window(x, y); //仍使用相对于父窗口的坐标
        if (hit != nullptr)
            return hit;
    }

    return hit;
}

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值