diff --git a/config.json b/config.json index 4ccf3826..514ec100 100644 --- a/config.json +++ b/config.json @@ -1,56 +1,99 @@ { - "homepage":"yoda://welcome", - "interface": + "window": { + "title":"Yoda", "theme":"Default", - "window": + "header": { - "header": + "enabled":true, + "button": { - "enabled":true, - "button": - { - "close":true - } - }, - "navigation": - { - "button": - { - "home":true, - "back":true, - "forward":true, - "reload":true, - "go":true - } - }, - "width":640, - "height":480 - } - }, - "resolver": - { - "enabled":true, - "request": - { - "timeout":1, - "host": - [ - "1.1.1.1", - "8.8.8.8" - ], - "record": - [ - "A", - "AAAA" - ] + "close":true + } }, - "result": + "width":640, + "height":480, + "tab": { - "shuffle":false, - "cache": + "page": { - "timeout":3600 + "resolver": + { + "enabled":true, + "request": + { + "timeout":1, + "host": + [ + "1.1.1.1", + "8.8.8.8" + ], + "record": + [ + "A", + "AAAA" + ] + }, + "result": + { + "shuffle":false, + "cache": + { + "timeout":3600 + } + } + }, + "header": + { + "margin":8, + "button": + { + "home": + { + "visible":true, + "label":"Home", + "url":"yoda://welcome" + }, + "back": + { + "visible":true, + "label":"Back" + }, + "forward": + { + "visible":true, + "label":"Forward" + }, + "go": + { + "visible":true, + "label":"Go" + } + }, + "address": + { + "placeholder":"URL or any search term...", + "length": + { + "max":1024 + } + } + }, + "body": + { + "margin":8 + }, + "footer": + { + "margin":8, + "status": + { + "open": + { + "complete":"{TIME_C} | {RESPONSE_META} | {RESPONSE_LENGTH} bytes | {RESPONSE_SECONDS} seconds" + } + } + } } } } diff --git a/src/Abstract/Window.php b/src/Abstract/Window.php deleted file mode 100644 index e6dff812..00000000 --- a/src/Abstract/Window.php +++ /dev/null @@ -1,70 +0,0 @@ -config = $config; - - $this->window = new \GtkWindow(); - - $this->window->set_size_request( - $this->config->interface->window->width, - $this->config->interface->window->height - ); - - if ($this->config->interface->window->header->enabled) - { - $header = new \GtkHeaderBar(); - - $header->set_show_close_button( - $this->config->interface->window->header->button->close - ); - - $this->window->set_titlebar( - $header - ); - } - - $this->window->set_title( - 'Yoda' - ); - - $this->window->connect( - 'destroy', - function() - { - \Gtk::main_quit(); - } - ); - } - - public function setTheme( - string $css - ): void - { - $this->css = new \GtkCssProvider(); - - $this->css->load_from_data( - $css - ); - - $this->style = new \GtkStyleContext(); - - $this->style->add_provider_for_screen( - $this->css, - 600 - ); - } -} \ No newline at end of file diff --git a/src/Controller/Browser.php b/src/Controller/Browser.php deleted file mode 100644 index 28f2641c..00000000 --- a/src/Controller/Browser.php +++ /dev/null @@ -1,273 +0,0 @@ -memory = new \Yggverse\Yoda\Model\Memory(); - - $this->tab = new \Yggverse\Yoda\Entity\Box\Tab(); - - $this->tab->navigation->address->entry->connect( - 'activate', - function ($entry) - { - $this->navigate( - $entry->get_text() - ); - } - ); - - if ($this->config->interface->window->navigation->button->go) - { - $this->tab->navigation->go->button->connect( - 'released', - function ($entry) - { - $this->navigate( - $this->tab->navigation->address->entry->get_text() - ); - } - ); - } - - if ($this->config->interface->window->navigation->button->reload) - { - $this->tab->navigation->reload->button->connect( - 'released', - function ($entry) - { - $this->navigate( - $this->tab->navigation->address->entry->get_text() - ); - } - ); - } - - if ($this->config->interface->window->navigation->button->home && $this->config->homepage) - { - $this->tab->navigation->home->button->connect( - 'released', - function ($entry) - { - $this->tab->navigation->address->entry->set_text( - $this->config->homepage - ); - - $this->navigate( - $this->config->homepage - ); - } - ); - - $this->tab->navigation->home->button->set_sensitive( - !($this->tab->navigation->address->entry->get_text() == $this->config->homepage) - ); - } - - $this->navigate( - $this->tab->navigation->address->entry->get_text() - ); - - $this->window->add( - $this->tab->box - ); - - $this->window->show_all(); - } - - public function navigate(string $url): void - { - switch (true) - { - case str_starts_with($url, 'gemini://'): - - $this->_gemini( - $url - ); - - break; - - case str_starts_with($url, 'yoda://'): - - $this->_yoda( - $url - ); - - break; - - default: - - $this->_yoda( - 'yoda://oops' - ); - } - } - - private function _yoda(string $url): void - { - if ($data = \Yggverse\Yoda\Model\Page::get(str_replace('yoda://', '', $url))) - { - $response = new \Yggverse\Gemini\Client\Response( - $data - ); - - $this->tab->content->label->set_text( - $data - ); - - $body = new \Yggverse\Gemini\Gemtext\Body( - $data - ); - - if ($h1 = $body->getH1()) - { - $this->window->set_title( - $h1[0] - ); - } - } - - else - { - $data = \Yggverse\Yoda\Model\Page::get('Oops'); - - $this->tab->content->label->set_text( - $data - ); - - $body = new \Yggverse\Gemini\Gemtext\Body( - $data - ); - - if ($h1 = $body->getH1()) - { - $this->window->set_title( - $h1[0] - ); - } - } - - if ($this->config->interface->window->navigation->button->home && $this->config->homepage) - { - $this->tab->navigation->home->button->set_sensitive( - !($url == $this->config->homepage) - ); - } - } - - private function _gemini(string $url): void - { - $this->tab->tray->label->set_text( - sprintf( - 'Open %s...', - urldecode( - $url - ) - ) - ); - - $start = microtime(true); - - $host = null; - - if ($this->config->resolver->enabled) - { - $address = new \Yggverse\Net\Address( - $url - ); - - $name = $address->getHost(); - - if (!$host = $this->memory->get($name)) - { - $resolve = new \Yggverse\Net\Resolve( - $this->config->resolver->request->record, - $this->config->resolver->request->host, - $this->config->resolver->request->timeout, - $this->config->resolver->result->shuffle - ); - - $resolved = $resolve->address( - $address - ); - - if ($resolved) - { - $host = $resolved->getHost(); - - $this->memory->set( - $name, - $host - ); - } - } - } - - $request = new \Yggverse\Gemini\Client\Request( - $url, - $host - ); - - $raw = $request->getResponse(); - - $end = microtime(true); - - $response = new \Yggverse\Gemini\Client\Response( - $raw - ); - - $this->tab->content->label->set_text( - $response->getBody() - ); - - $body = new \Yggverse\Gemini\Gemtext\Body( - $response->getBody() - ); - - if ($h1 = $body->getH1()) - { - $this->window->set_title( - sprintf( - '%s - Yoda', - empty($h1[0]) ? $address->getHost() : $h1[0] - ) - ); - } - - $this->tab->tray->label->set_text( - sprintf( - '%s | %s | %d bytes | %s seconds', - date('c'), - $response->getMeta() ? $response->getMeta() : $response->getCode(), - number_format( - mb_strlen( - $raw - ) - ), - round( - $end - $start, 2 - ) - ) - ); - - if ($this->config->interface->window->navigation->button->home && $this->config->homepage) - { - $this->tab->navigation->home->button->set_sensitive( - !($url == $this->config->homepage) - ); - } - } -} \ No newline at end of file diff --git a/src/Entity/Box/Menu.php b/src/Entity/Box/Menu.php deleted file mode 100644 index 66cbf9b3..00000000 --- a/src/Entity/Box/Menu.php +++ /dev/null @@ -1,30 +0,0 @@ -box = new \GtkBox( - \GtkOrientation::VERTICAL - ); - - $this->box->set_name( - $name - ); - - $this->main = new \Yggverse\Yoda\Entity\Menu\Bar\Main(); - - $this->box->add( - $this->main->bar - ); - } -} \ No newline at end of file diff --git a/src/Entity/Box/Navigation.php b/src/Entity/Box/Navigation.php deleted file mode 100644 index fcf6e948..00000000 --- a/src/Entity/Box/Navigation.php +++ /dev/null @@ -1,123 +0,0 @@ -config = \Yggverse\Yoda\Model\File::getConfig(); - - $this->box = new \GtkBox( - \GtkOrientation::HORIZONTAL - ); - - $this->box->set_name( - $name - ); - - if ($this->config->interface->window->navigation->button->home && $this->config->homepage) - { - $this->home = new \Yggverse\Yoda\Entity\Button\Home(); - - $this->box->pack_start( - $this->home->button, - false, - false, - 8 - ); - } - - if ($this->config->interface->window->navigation->button->back || $this->config->interface->window->navigation->button->forward) - { - $boxBackForward = new \GtkButtonBox( - \GtkOrientation::HORIZONTAL - ); - - $boxBackForward->set_layout( - \GtkButtonBoxStyle::EXPAND - ); - - if ($this->config->interface->window->navigation->button->back) - { - $this->back = new \Yggverse\Yoda\Entity\Button\Back(); - - $boxBackForward->pack_start( - $this->back->button, - false, - true, - 0 - ); - } - - if ($this->config->interface->window->navigation->button->forward) - { - $this->forward = new \Yggverse\Yoda\Entity\Button\Forward(); - - $boxBackForward->pack_end( - $this->forward->button, - false, - true, - 0 - ); - } - - $this->box->pack_start( - $boxBackForward, - false, - false, - 8 - ); - } - - if ($this->config->interface->window->navigation->button->reload) - { - $this->reload = new \Yggverse\Yoda\Entity\Button\Reload(); - - $this->box->pack_start( - $this->reload->button, - false, - false, - 8 - ); - } - - $this->address = new \Yggverse\Yoda\Entity\Entry\Address( - $this->config->homepage - ); - - $this->box->pack_start( - $this->address->entry, - true, - true, - 8 - ); - - if ($this->config->interface->window->navigation->button->go) - { - $this->go = new \Yggverse\Yoda\Entity\Button\Go(); - - $this->box->pack_end( - $this->go->button, - false, - false, - 8 - ); - } - } -} \ No newline at end of file diff --git a/src/Entity/Box/Tab.php b/src/Entity/Box/Tab.php deleted file mode 100644 index e9e7e57e..00000000 --- a/src/Entity/Box/Tab.php +++ /dev/null @@ -1,71 +0,0 @@ -box = new \GtkBox( - \GtkOrientation::VERTICAL - ); - - $this->box->set_name( - $name - ); - - // Init dependencies - $this->menu = new \Yggverse\Yoda\Entity\Box\Menu(); - - $this->box->pack_start( - $this->menu->box, - false, - true, - 0 - ); - - $this->navigation = new \Yggverse\Yoda\Entity\Box\Navigation(); - - $this->box->pack_start( - $this->navigation->box, - false, - true, - 8 - ); - - $this->content = new \Yggverse\Yoda\Entity\Label\Content(); - - $scroll = new \GtkScrolledWindow(); - - $scroll->add( - $this->content->label - ); - - $this->box->pack_start( - $scroll, - true, - true, - 0 - ); - - $this->tray = new \Yggverse\Yoda\Entity\Label\Tray(); - - $this->box->pack_start( - $this->tray->label, - false, - true, - 0 - ); - } -} \ No newline at end of file diff --git a/src/Entity/Button/Back.php b/src/Entity/Button/Back.php deleted file mode 100644 index e8b961f8..00000000 --- a/src/Entity/Button/Back.php +++ /dev/null @@ -1,18 +0,0 @@ -button = \GtkButton::new_with_label( - $label - ); - } -} \ No newline at end of file diff --git a/src/Entity/Button/Forward.php b/src/Entity/Button/Forward.php deleted file mode 100644 index 47416fd0..00000000 --- a/src/Entity/Button/Forward.php +++ /dev/null @@ -1,18 +0,0 @@ -button = \GtkButton::new_with_label( - $label - ); - } -} \ No newline at end of file diff --git a/src/Entity/Button/Go.php b/src/Entity/Button/Go.php deleted file mode 100644 index da09c427..00000000 --- a/src/Entity/Button/Go.php +++ /dev/null @@ -1,18 +0,0 @@ -button = \GtkButton::new_with_label( - $label - ); - } -} \ No newline at end of file diff --git a/src/Entity/Button/Home.php b/src/Entity/Button/Home.php deleted file mode 100644 index 1c94f1f9..00000000 --- a/src/Entity/Button/Home.php +++ /dev/null @@ -1,18 +0,0 @@ -button = \GtkButton::new_with_label( - $label - ); - } -} \ No newline at end of file diff --git a/src/Entity/Button/Reload.php b/src/Entity/Button/Reload.php deleted file mode 100644 index 37b74b04..00000000 --- a/src/Entity/Button/Reload.php +++ /dev/null @@ -1,18 +0,0 @@ -button = \GtkButton::new_with_label( - $label - ); - } -} \ No newline at end of file diff --git a/src/Entity/Entry/Address.php b/src/Entity/Entry/Address.php deleted file mode 100644 index 7d037643..00000000 --- a/src/Entity/Entry/Address.php +++ /dev/null @@ -1,29 +0,0 @@ -entry = new \GtkEntry(); - - $this->entry->set_text( - $text - ); - - $this->entry->set_placeholder_text( - $placeholder - ); - - $this->entry->set_max_length( - 1024 - ); - } -} \ No newline at end of file diff --git a/src/Entity/Label/Content.php b/src/Entity/Label/Content.php deleted file mode 100644 index e73eafb8..00000000 --- a/src/Entity/Label/Content.php +++ /dev/null @@ -1,37 +0,0 @@ -label = new \GtkLabel( - $value - ); - - $this->label->set_use_markup( - true - ); - - $this->label->set_selectable( - true - ); - - $this->label->set_line_wrap( - true - ); - - $this->label->set_xalign( - 0 - ); - - $this->label->set_yalign( - 0 - ); - } -} \ No newline at end of file diff --git a/src/Entity/Label/Tray.php b/src/Entity/Label/Tray.php deleted file mode 100644 index 824a8251..00000000 --- a/src/Entity/Label/Tray.php +++ /dev/null @@ -1,33 +0,0 @@ -label = new \GtkLabel( - $value - ); - - $this->label->set_use_markup( - true - ); - - $this->label->set_selectable( - false - ); - - $this->label->set_xalign( - 0 - ); - - $this->label->set_yalign( - 0 - ); - } -} \ No newline at end of file diff --git a/src/Entity/Menu/Bar/Main.php b/src/Entity/Menu/Bar/Main.php deleted file mode 100644 index c4c56751..00000000 --- a/src/Entity/Menu/Bar/Main.php +++ /dev/null @@ -1,23 +0,0 @@ -bar = new \GtkMenuBar(); - - $this->yoda = new \Yggverse\Yoda\Entity\Menu\Item\Yoda(); - - $this->bar->append( - $this->yoda->item - ); - } -} \ No newline at end of file diff --git a/src/Entity/Menu/Item/Quit.php b/src/Entity/Menu/Item/Quit.php deleted file mode 100644 index 58f25e38..00000000 --- a/src/Entity/Menu/Item/Quit.php +++ /dev/null @@ -1,30 +0,0 @@ -item = \GtkMenuItem::new_with_label( - $label - ); - - $this->activate(); - } - - public function activate(): void - { - $this->item->connect( - 'activate', - function () - { - \Gtk::main_quit(); - } - ); - } -} \ No newline at end of file diff --git a/src/Entity/Menu/Item/Yoda.php b/src/Entity/Menu/Item/Yoda.php deleted file mode 100644 index f51baaae..00000000 --- a/src/Entity/Menu/Item/Yoda.php +++ /dev/null @@ -1,29 +0,0 @@ -item = \GtkMenuItem::new_with_label( - $label - ); - - $children = new \GtkMenu(); - - $quit = new \Yggverse\Yoda\Entity\Menu\Item\Quit(); - - $children->append( - $quit->item - ); - - $this->item->set_submenu( - $children - ); - } -} \ No newline at end of file diff --git a/src/Tab/Page.php b/src/Tab/Page.php new file mode 100644 index 00000000..e799b53f --- /dev/null +++ b/src/Tab/Page.php @@ -0,0 +1,525 @@ +config = \Yggverse\Yoda\Model\File::getConfig()->window->tab->page; + + // Init memory + $this->memory = new \Yggverse\Yoda\Model\Memory(); + + // Compose header + $this->header = new \GtkBox( + \GtkOrientation::HORIZONTAL + ); + + $this->header->set_margin_top( + $this->config->header->margin + ); + + $this->header->set_margin_bottom( + $this->config->header->margin + ); + + $this->header->set_margin_start( + $this->config->header->margin + ); + + $this->header->set_margin_end( + $this->config->header->margin + ); + + $this->header->set_spacing( + $this->config->header->margin + ); + + // Home button + $this->home = \GtkButton::new_with_label( + $this->config->header->button->home->label + ); + + $this->home->set_sensitive( + !($url == $this->config->header->button->home->url) + ); + + $this->home->connect( + 'released', + function ($entry) + { + $this->open( + $this->config->header->button->home->url + ); + } + ); + + if ($this->config->header->button->home->visible) + { + $this->header->add( + $this->home + ); + } + + // Back button + $this->back = \GtkButton::new_with_label( + $this->config->header->button->back->label + ); + + // Forward button + $this->forward = \GtkButton::new_with_label( + $this->config->header->button->forward->label + ); + + /// Group buttons + if ($this->config->header->button->back->visible || $this->config->header->button->forward->visible) + { + $buttonGroup = new \GtkButtonBox( + \GtkOrientation::HORIZONTAL + ); + + $buttonGroup->set_layout( + \GtkButtonBoxStyle::EXPAND + ); + + if ($this->config->header->button->back->visible) + { + $buttonGroup->add( + $this->back + ); + } + + if ($this->config->header->button->forward->visible) + { + $buttonGroup->add( + $this->forward + ); + } + + $this->header->add( + $buttonGroup + ); + } + + // Address field + $this->address = new \GtkEntry(); + + if ($url) + { + $this->address->set_text( + $url + ); + } + + $this->address->set_placeholder_text( + $this->config->header->address->placeholder + ); + + $this->address->set_max_length( + $this->config->header->address->length->max + ); + + $this->address->connect( + 'activate', + function ($entry) + { + $this->open( + $entry->get_text() + ); + } + ); + + $this->header->pack_start( + $this->address, + true, + true, + 0 + ); + + // Go button + $this->go = \GtkButton::new_with_label( + $this->config->header->button->go->label + ); + + $this->go->connect( + 'released', + function ($entry) + { + $this->open( + $this->address->get_text() + ); + } + ); + + if ($this->config->header->button->go->visible) + { + $this->header->add( + $this->go + ); + } + + // Compose body + $this->content = new \GtkLabel(); + + $this->content->set_use_markup( + true + ); + + $this->content->set_selectable( + true + ); + + $this->content->set_line_wrap( + true + ); + + $this->content->set_xalign( + 0 + ); + + $this->content->set_yalign( + 0 + ); + + // Init scrolled container + $this->container = new \GtkScrolledWindow(); + + $this->container->add( + $this->content + ); + + $this->body = new \GtkBox( + \GtkOrientation::VERTICAL + ); + + $this->body->set_margin_start( + $this->config->body->margin + ); + + $this->body->pack_start( + $this->container, + true, + true, + 0 + ); + + // Compose footer + $this->footer = new \GtkBox( + \GtkOrientation::HORIZONTAL + ); + + $this->footer->set_margin_top( + $this->config->footer->margin + ); + + $this->footer->set_margin_bottom( + $this->config->footer->margin + ); + + $this->footer->set_margin_start( + $this->config->footer->margin + ); + + $this->footer->set_margin_end( + $this->config->footer->margin + ); + + $this->footer->set_spacing( + $this->config->footer->margin + ); + + $this->status = new \GtkLabel(); + + $this->footer->add( + $this->status + ); + + // Compose page + $this->box = new \GtkBox( + \GtkOrientation::VERTICAL + ); + + $this->box->add( + $this->header + ); + + $this->box->pack_start( + $this->body, + true, + true, + 0 + ); + + $this->box->add( + $this->footer + ); + } + + public function open( + string $url + ): void + { + // Update address field by requested + $this->address->set_text( + $url + ); + + // Update home button sensitivity on match requested + $this->home->set_sensitive( + !($url == $this->config->header->button->home->url) + ); + + // Open current address + switch (true) + { + case str_starts_with($url, 'gemini://'): + + $this->_gemini( + $url + ); + + break; + + default: + + $this->_yoda( + $url + ); + } + } + + private function _gemini(string $url): void + { + // Track response time + $start = microtime(true); + + // Init custom resolver + $host = null; + + if ($this->config->resolver->enabled) + { + $address = new \Yggverse\Net\Address( + $url + ); + + $name = $address->getHost(); + + if (!$host = $this->memory->get($name)) + { + $resolve = new \Yggverse\Net\Resolve( + $this->config->resolver->request->record, + $this->config->resolver->request->host, + $this->config->resolver->request->timeout, + $this->config->resolver->result->shuffle + ); + + $resolved = $resolve->address( + $address + ); + + if ($resolved) + { + $host = $resolved->getHost(); + + $this->memory->set( + $name, + $host + ); + } + } + } + + $request = new \Yggverse\Gemini\Client\Request( + $url, + $host + ); + + $raw = $request->getResponse(); + + $end = microtime(true); + + $response = new \Yggverse\Gemini\Client\Response( + $raw + ); + + $this->content->set_markup( + $this->_gemtext( + $response->getBody() + ) + ); + + /* @TODO + $body = new \Yggverse\Gemini\Gemtext\Body( + $response->getBody() + ); + + if ($h1 = $body->getH1()) + { + $this->window->set_title( + sprintf( + '%s - Yoda', + empty($h1[0]) ? $address->getHost() : $h1[0] + ) + ); + } + */ + + $this->status->set_text( + str_replace( // Custom macros mask from config.json + [ + '{NAVIGATION_ADDRESS}', + '{TIME_C}', + '{RESPONSE_META}', + '{RESPONSE_LENGTH}', + '{RESPONSE_SECONDS}' + ], + [ + urlencode( + $url + ), + date( + 'c' + ), + $response->getMeta() ? $response->getMeta() : $response->getCode(), + number_format( + mb_strlen( + $raw + ) + ), + round( + $end - $start, 2 + ) + ], + $this->config->footer->status->open->complete + ) + ); + } + + private function _yoda( + string $url + ): void + { + // Load local page + if (!$data = \Yggverse\Yoda\Model\Page::get(str_replace('yoda://', '', $url))) + { + $data = \Yggverse\Yoda\Model\Page::get('Oops'); + } + + $this->content->set_markup( + $this->_gemtext( + $data + ) + ); + + // Parse gemtext + /* @TODO + $body = new \Yggverse\Gemini\Gemtext\Body( + $data + ); + + if ($h1 = $body->getH1()) + { + $this->window->set_title( + $h1[0] + ); + } + */ + } + + private function _gemtext( + string $gemtext + ): string + { + // Format body + $body = new \Yggverse\Gemini\Gemtext\Body( + $gemtext + ); + + $lines = $body->getLines(); + + $escaped = []; + + /// Format H1 + foreach ($body->getH1() as $index => $h1) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h1 + ) + ); + + $escaped[] = $index; + } + + /// Format H2 + foreach ($body->getH2() as $index => $h2) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h2 + ) + ); + + $escaped[] = $index; + } + + /// Format H3 + foreach ($body->getH3() as $index => $h3) + { + $lines[$index] = sprintf( + '%s', + htmlentities( + $h3 + ) + ); + + $escaped[] = $index; + } + + /// Escape entities + foreach ($lines as $index => $line) + { + if (!in_array($index, $escaped)) + { + $lines[$index] = htmlentities( + $line + ); + } + } + + // @TODO links, code, escape entities + + return implode( + PHP_EOL, + $lines + ); + } +} \ No newline at end of file diff --git a/src/Yoda.php b/src/Yoda.php index 12d1c134..1c24ded0 100644 --- a/src/Yoda.php +++ b/src/Yoda.php @@ -9,6 +9,56 @@ require_once __DIR__ . // Init app \Gtk::init(); -new \Yggverse\Yoda\Controller\Browser(); +$config = \Yggverse\Yoda\Model\File::getConfig(); // @TODO + +$window = new \GtkWindow(); + +$window->set_size_request( + $config->window->width, + $config->window->height +); + +if ($config->window->header->enabled) +{ + $header = new \GtkHeaderBar(); + + $header->set_title( + $config->window->title + ); + + $header->set_show_close_button( + $config->window->header->button->close + ); + + $window->set_titlebar( + $header + ); +} + +$window->connect( + 'destroy', + function() + { + \Gtk::main_quit(); + } +); + +$page = new \Yggverse\Yoda\Tab\Page(); + +$page->open( + 'yoda://welcome' +); + +$tab = new \GtkNotebook(); + +$tab->add( + $page->box +); + +$window->add( + $tab +); + +$window->show_all(); \Gtk::main(); \ No newline at end of file