[gnome-weather/wip/ewlsh/gtk4] Migrate widgets to templates and GTK4 hooks
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-weather/wip/ewlsh/gtk4] Migrate widgets to templates and GTK4 hooks
- Date: Sun, 2 Jan 2022 06:27:15 +0000 (UTC)
commit fb1cf51b8b2e1e543ebba53f42de47842b01a5a0
Author: Evan Welsh <contact evanwelsh com>
Date: Sun Jan 2 00:25:24 2022 -0600
Migrate widgets to templates and GTK4 hooks
data/application.css | 9 +
data/weather-widget.ui | 366 ++++++++++++++---------------
data/window.ui | 15 +-
src/app/city.js | 101 +++-----
src/app/dailyForecast.js | 328 ++++++++++----------------
src/app/entry.js | 12 +-
src/app/hourlyForecast.js | 2 -
src/app/main.js | 308 ++++++++++++------------
src/app/thermometer.js | 231 +++++++++---------
src/app/thermometer.ui | 20 ++
src/app/window.js | 29 +--
src/misc/util.js | 8 +-
src/org.gnome.Weather.src.gresource.xml.in | 1 +
13 files changed, 665 insertions(+), 765 deletions(-)
---
diff --git a/data/application.css b/data/application.css
index 6aaf9a3..f6c7cc1 100644
--- a/data/application.css
+++ b/data/application.css
@@ -51,6 +51,15 @@
color: #c89009;
}
+WeatherThermometerScale {
+ margin: 12px;
+}
+
+WeatherThermometerScale > .inner {
+ border-radius: 50% / 12px;
+ background-image: linear-gradient(#2174d9, #c89009);
+}
+
WeatherThermometer > label.high {
font-weight: bold;
color: #c89009;
diff --git a/data/weather-widget.ui b/data/weather-widget.ui
index 2acc50f..372772f 100644
--- a/data/weather-widget.ui
+++ b/data/weather-widget.ui
@@ -2,229 +2,229 @@
<interface>
<requires lib="gtk" version="4.0"/>
<template class="Gjs_WeatherWidget">
-
-
<child>
- <object class="AdwClamp" id="clamp">
- <property name="maximum_size">1010</property>
- <property name="tightening_threshold">600</property>
+ <object class="GtkBox" id="outerBox">
+ <property name="orientation">vertical</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="margin-top">18</property>
+ <property name="margin-bottom">18</property>
+ <!-- <property name="spacing">18</property> -->
<child>
- <object class="GtkBox" id="outerBox">
- <property name="orientation">vertical</property>
- <property name="margin-start">18</property>
- <property name="margin-end">18</property>
- <property name="margin-top">18</property>
- <property name="margin-bottom">18</property>
- <!-- <property name="spacing">18</property> -->
+ <object class="GtkGrid">
+ <property name="name">conditions-grid</property>
+ <property name="column_spacing">10</property>
<child>
- <object class="GtkGrid">
- <property name="name">conditions-grid</property>
- <property name="column_spacing">10</property>
+ <object class="GtkImage" id="conditionsImage">
+ <!-- <property name="name">conditions-image</property> -->
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="pixel_size">84</property>
+ <style>
+ <class name="icon-dropshadow"/>
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="placesButton">
+ <property name="receives_default">1</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
<child>
- <object class="GtkImage" id="conditionsImage">
- <!-- <property name="name">conditions-image</property> -->
- <property name="halign">start</property>
- <property name="valign">center</property>
- <property name="pixel_size">84</property>
- <style>
- <class name="icon-dropshadow"/>
- </style>
- <layout>
- <property name="column">0</property>
- <property name="row">0</property>
- <property name="row-span">2</property>
- </layout>
+ <object class="GtkBox" id="placesBox">
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="placesLabel">
+ <property name="name">places-label</property>
+ <property name="wrap">1</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="label" translatable="yes">Places</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ </child>
</object>
</child>
+ <style>
+ <class name="text-button"/>
+ <class name="flat"/>
+ </style>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="temperatureBox">
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="spacing">8</property> -->
+ <property name="baseline_position">bottom</property>
<child>
- <object class="GtkMenuButton" id="placesButton">
- <property name="receives_default">1</property>
+ <object class="GtkLabel" id="temperatureLabel">
+ <property name="name">temperature-label</property>
<property name="halign">start</property>
- <property name="valign">start</property>
- <child>
- <object class="GtkBox" id="placesBox">
- <property name="spacing">12</property>
- <child>
- <object class="GtkLabel" id="placesLabel">
- <property name="name">places-label</property>
- <property name="wrap">1</property>
- <property name="wrap-mode">word-char</property>
- <property name="label" translatable="yes">Places</property>
- </object>
- </child>
- <child>
- <object class="GtkImage">
- <property name="icon_name">pan-down-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- <style>
- <class name="text-button"/>
- <class name="flat"/>
- </style>
- <layout>
- <property name="column">1</property>
- <property name="row">0</property>
- </layout>
+ <property name="valign">baseline</property>
</object>
</child>
<child>
- <object class="GtkBox" id="temperatureBox">
+ <object class="GtkLabel" id="apparentLabel">
+ <property name="name">apparent-label</property>
<property name="halign">start</property>
- <property name="valign">start</property>
- <property name="spacing">8</property> -->
- <property name="baseline_position">bottom</property>
- <child>
- <object class="GtkLabel" id="temperatureLabel">
- <property name="name">temperature-label</property>
- <property name="halign">start</property>
- <property name="valign">baseline</property>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="apparentLabel">
- <property name="name">apparent-label</property>
- <property name="halign">start</property>
- <property name="valign">baseline</property>
- </object>
- </child>
- <layout>
- <property name="column">1</property>
- <property name="row">1</property>
- </layout>
+ <property name="valign">baseline</property>
</object>
</child>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
</object>
</child>
- <child>
- <object class="GtkOverlay">
- <property name="child">
- <!-- <object class="GtkFrame" id="forecastFrame">
+ </object>
+ </child>
+ <child>
+ <object class="GtkOverlay">
+ <property name="child">
+ <!-- <object class="GtkFrame" id="forecastFrame">
<property name="name">forecast-frame</property>
<property name="child"> -->
- <object class="AdwViewStack" id="forecastStack">
- <!-- <property name="transition_type">crossfade</property> -->
- <child>
- <object class="AdwViewStackPage">
- <property name="name">hourly</property>
- <property name="title" translatable="yes">Hourly</property>
- <property name="icon-name">preferences-system-time-symbolic</property>
- <property name="child">
- <object class="GtkScrolledWindow" id="forecastHourly">
- <property name="focusable">1</property>
- <property name="vscrollbar_policy">never</property>
- <property name="min_content_width">308</property>
- <property name="child">
- <object class="GtkViewport" id="forecastHourlyViewport">
- <property name="hscroll_policy">natural</property>
- <property name="vscroll_policy">natural</property>
- </object>
- </property>
- </object>
+ <object class="AdwViewStack" id="forecastStack">
+ <child>
+ <object class="AdwViewStackPage">
+ <property name="name">hourly</property>
+ <property name="title" translatable="yes">Hourly</property>
+ <property name="icon-name">preferences-system-time-symbolic</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="focusable">1</property>
+ <property name="vscrollbar_policy">never</property>
+ <property name="hscrollbar_policy">external</property>
+ <property name="hadjustment">
+ <object class="GtkAdjustment" id="forecastHourlyAdjustment" />
</property>
- </object>
- </child>
- <child>
- <object class="AdwViewStackPage">
- <property name="name">daily</property>
- <property name="title" translatable="yes">Daily</property>
- <property name="icon-name">x-office-calendar-symbolic</property>
+ <property name="min_content_width">308</property>
<property name="child">
- <object class="GtkScrolledWindow" id="forecastDaily">
- <property name="focusable">1</property>
- <property name="vscrollbar_policy">never</property>
- <property name="min_content_width">308</property>
- <property name="child">
- <object class="GtkViewport" id="forecastDailyViewport">
- <property name="hscroll_policy">natural</property>
- <property name="vscroll_policy">natural</property>
- </object>
- </property>
+ <object class="GtkViewport">
+ <property name="hscroll_policy">natural</property>
+ <property name="vscroll_policy">natural</property>
+ <child>
+ <object class="Gjs_HourlyForecastBox" id="forecastHourly" />
+ </child>
</object>
</property>
</object>
- </child>
- </object>
- <!-- </property>
- </object> -->
- </property>
- <child type="overlay">
- <object class="GtkButton" id="rightButton">
- <property name="focusable">1</property>
- <property name="receives_default">1</property>
- <property name="halign">end</property>
- <property name="valign">center</property>
- <property name="margin_end">28</property>
- <child>
- <object class="GtkImage" id="right-image">
- <property name="icon_name">go-next-symbolic</property>
- </object>
- </child>
- <style>
- <class name="osd"/>
- <class name="circular"/>
- </style>
+ </property>
</object>
</child>
- <child type="overlay">
- <object class="GtkButton" id="leftButton">
- <property name="focusable">1</property>
- <property name="receives_default">1</property>
- <property name="halign">start</property>
- <property name="valign">center</property>
- <property name="margin_start">28</property>
- <child>
- <object class="GtkImage" id="left-image">
- <property name="icon_name">go-previous-symbolic</property>
- <property name="icon_size">1</property>
+ <child>
+ <object class="AdwViewStackPage">
+ <property name="name">daily</property>
+ <property name="title" translatable="yes">Daily</property>
+ <property name="icon-name">x-office-calendar-symbolic</property>
+ <property name="child">
+ <object class="GtkScrolledWindow">
+ <property name="focusable">1</property>
+ <property name="vscrollbar_policy">never</property>
+ <property name="hscrollbar_policy">external</property>
+ <property name="hadjustment">
+ <object class="GtkAdjustment" id="forecastDailyAdjustment" />
+ </property>
+ <property name="min_content_width">308</property>
+ <property name="child">
+ <object class="GtkViewport">
+ <property name="hscroll_policy">natural</property>
+ <property name="vscroll_policy">natural</property>
+ <child>
+ <object class="Gjs_DailyForecastBox" id="forecastDaily" />
+ </child>
+ </object>
+ </property>
</object>
- </child>
- <style>
- <class name="osd"/>
- <class name="circular"/>
- </style>
+ </property>
</object>
</child>
</object>
- </child>
- <child>
- <object class="GtkGrid">
- <property name="row_spacing">8</property>
+ </property>
+ <child type="overlay">
+ <object class="GtkButton" id="rightButton">
+ <property name="focusable">1</property>
+ <property name="receives_default">1</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="margin_end">28</property>
<child>
- <object class="GtkLabel" id="updatedTimeLabel">
- <property name="name">updated-time-label</property>
- <property name="halign">start</property>
- <layout>
- <property name="column">0</property>
- <property name="row">0</property>
- </layout>
+ <object class="GtkImage" id="right-image">
+ <property name="icon_name">go-next-symbolic</property>
</object>
</child>
+ <style>
+ <class name="osd"/>
+ <class name="circular"/>
+ </style>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkButton" id="leftButton">
+ <property name="focusable">1</property>
+ <property name="receives_default">1</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="margin_start">28</property>
<child>
- <object class="GtkLabel" id="attributionLabel">
- <property name="name">attribution-label</property>
- <property name="use_markup">1</property>
- <property name="wrap">1</property>
- <!-- <property name="track_visited_links">False</property> -->
- <property name="xalign">0</property>
- <layout>
- <property name="column">0</property>
- <property name="row">1</property>
- </layout>
+ <object class="GtkImage" id="left-image">
+ <property name="icon_name">go-previous-symbolic</property>
+ <property name="icon_size">1</property>
</object>
</child>
+ <style>
+ <class name="osd"/>
+ <class name="circular"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="row_spacing">8</property>
+ <child>
+ <object class="GtkLabel" id="updatedTimeLabel">
+ <property name="name">updated-time-label</property>
+ <property name="halign">start</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="attributionLabel">
+ <property name="name">attribution-label</property>
+ <property name="use_markup">1</property>
+ <property name="wrap">1</property>
+ <property name="xalign">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
</object>
</child>
- <layout>
- <property name="column">0</property>
- <property name="row">2</property>
- </layout>
</object>
</child>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
</object>
</child>
-
-
</template>
</interface>
diff --git a/data/window.ui b/data/window.ui
index a46ec83..7478f6b 100644
--- a/data/window.ui
+++ b/data/window.ui
@@ -31,9 +31,8 @@
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar" id="header">
- <!-- <property name="show_close_button">True</property> -->
- <!-- <property name="centering_policy">strict</property> -->
- <child>
+ <property name="centering_policy">strict</property>
+ <child type="start">
<object class="GtkRevealer" id="refreshRevealer">
<property name="transition_type">crossfade</property>
<property name="child">
@@ -77,7 +76,6 @@
<property name="name">city</property>
<property name="child">
<object class="AdwViewSwitcherTitle" id="forecastStackSwitcher">
-
<property name="title" translatable="yes">Weather</property>
</object>
</property>
@@ -85,7 +83,7 @@
</child>
</object>
</child>
- <child>
+ <child type="end">
<object class="GtkMenuButton">
<property name="focusable">1</property>
<property name="valign">center</property>
@@ -101,20 +99,20 @@
</child>
<child>
<object class="GtkStack" id="stack">
-
+
<property name="transition_type">crossfade</property>
<child>
<object class="GtkStackPage">
<property name="name">search</property>
<property name="child">
<object class="AdwStatusPage" id="searchView">
-
+
<property name="icon_name">mark-location-symbolic</property>
<property name="title" translatable="yes">Welcome to Weather!</property>
<property name="description" translatable="yes">To get started, select a
location.</property>
<child>
<object class="Gjs_LocationSearchEntry" id="searchEntry">
- <!-- <property name="hexpand">False</property>
+ <!-- TODO: Our custom widget doesn't have these properties <property
name="hexpand">False</property>
<property name="halign">center</property>
<property name="width-request">246</property> -->
<!-- <property name="placeholder_text" translatable="yes">Search for a city or
country</property> -->
@@ -132,7 +130,6 @@
</child>
<child>
<object class="AdwViewSwitcherBar" id="forecastStackSwitcherBar">
-
<property name="reveal" bind-source="forecastStackSwitcher" bind-property="title-visible"
bind-flags="sync-create"/>
</object>
</child>
diff --git a/src/app/city.js b/src/app/city.js
index 9fd535f..16d62a6 100644
--- a/src/app/city.js
+++ b/src/app/city.js
@@ -16,6 +16,7 @@
// with Gnome Weather; if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+const Adw = imports.gi.Adw;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
@@ -37,7 +38,6 @@ const UPDATED_TIME_TIMEOUT = 60; //s
var WeatherWidget = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/weather-widget.ui',
InternalChildren: [
- 'clamp',
'conditionsImage',
'placesButton',
'temperatureLabel',
@@ -46,37 +46,31 @@ var WeatherWidget = GObject.registerClass({
'leftButton',
'rightButton',
'forecastHourly',
- 'forecastHourlyViewport',
+ 'forecastHourlyAdjustment',
'forecastDaily',
- 'forecastDailyViewport',
+ 'forecastDailyAdjustment',
'updatedTimeLabel',
- 'attributionLabel'],
+ 'attributionLabel'
+ ],
}, class WeatherWidget extends Gtk.Widget {
_init(application, window) {
super._init({
name: 'weather-page'
});
+ Object.assign(this.layoutManager, {
+ maximumSize: 1010,
+ tighteningThreshold: 600,
+ });
+
this._info = null;
this._worldView = new WorldView.WorldContentView(application, window);
this._placesButton.set_popover(this._worldView);
- this._forecasts = {
- hourly: new HourlyForecast.HourlyForecastBox(),
- daily: new DailyForecast.DailyForecastBox(),
- };
- this._forecastHourlyViewport.set_child(this._forecasts.hourly);
- this._forecastDailyViewport.set_child(this._forecasts.daily);
-
- for (const scrollWindow of [this._forecastHourly, this._forecastDaily]) {
- let hscrollbar = scrollWindow.get_hscrollbar();
- hscrollbar.set_opacity(0.0);
- hscrollbar.hide();
-
- let hadjustment = scrollWindow.get_hadjustment();
- hadjustment.connect('changed', () => this._syncLeftRightButtons());
- hadjustment.connect('value-changed', () => this._syncLeftRightButtons());
+ for (const adjustment of [this._forecastHourlyAdjustment, this._forecastDailyAdjustment]) {
+ adjustment.connect('changed', () => this._syncLeftRightButtons());
+ adjustment.connect('value-changed', () => this._syncLeftRightButtons());
}
this._forecastStack.connect('notify::visible-child', () => {
@@ -110,50 +104,24 @@ var WeatherWidget = GObject.registerClass({
this._beginScrollAnimation(target);
});
- // this._forecastFrame.connect('draw', (frame, cr) => {
- // const width = frame.get_allocated_width();
- // const height = frame.get_allocated_height();
-
- // const borderRadius = 8;
-
- // const arc0 = 0.0;
- // const arc1 = Math.PI * 0.5
- // const arc2 = Math.PI;
- // const arc3 = Math.PI * 1.5
-
- // cr.newSubPath();
- // cr.arc(width - borderRadius, borderRadius, borderRadius, arc3, arc0);
- // cr.arc(width - borderRadius, height - borderRadius, borderRadius, arc0, arc1);
- // cr.arc(borderRadius, height - borderRadius, borderRadius, arc1, arc2);
- // cr.arc(borderRadius, borderRadius, borderRadius, arc2, arc3);
- // cr.closePath();
-
- // cr.clip();
- // cr.fill();
-
- // return false;
- // });
-
this._updatedTime = null;
this._updatedTimeTimeoutId = 0;
-
- this.connect('destroy', () => this._onDestroy());
}
- // vfunc_measure(orientation, for_size) {
- // return this._box.measure(orientation, for_size);
- // }
+ vfunc_unroot() {
+ this._worldView.unparent();
+ this._worldView = null;
- _cleanup() {
- // this._contentGrid.unparent();
- this._worldView._cleanup();
+ super.vfunc_unroot();
}
- _onDestroy() {
+ vfunc_unmap() {
if (this._updatedTimeTimeoutId) {
GLib.Source.remove(this._updatedTimeTimeoutId);
this._updatedTimeTimeoutId = 0;
}
+
+ super.vfunc_unmap();
}
_syncLeftRightButtons() {
@@ -202,8 +170,8 @@ var WeatherWidget = GObject.registerClass({
}
clear() {
- for (let t of ['hourly', 'daily'])
- this._forecasts[t].clear();
+ this._forecastHourly.clear();
+ this._forecastDaily.clear();
if (this._tickId) {
this.remove_tick_callback(this._tickId);
@@ -242,8 +210,8 @@ var WeatherWidget = GObject.registerClass({
const [, apparentValue] = info.get_value_apparent(GWeather.TemperatureUnit.DEFAULT);
this._apparentLabel.label = _('Feels like %.0f°').format(apparentValue);
- for (let t of ['hourly', 'daily'])
- this._forecasts[t].update(info);
+ this._forecastHourly.update(info);
+ this._forecastDaily.update(info);
if (this._updatedTimeTimeoutId)
GLib.Source.remove(this._updatedTimeTimeoutId);
@@ -303,12 +271,11 @@ var WeatherWidget = GObject.registerClass({
}
});
-WeatherWidget.set_layout_manager_type(Gtk.BoxLayout);
+WeatherWidget.set_layout_manager_type(Adw.ClampLayout);
var WeatherView = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/city.ui',
-
InternalChildren: ['spinner', 'stack']
}, class WeatherView extends Gtk.Widget {
@@ -321,14 +288,14 @@ var WeatherView = GObject.registerClass({
this._info = null;
this._updateId = 0;
- this.connect('destroy', () => this._onDestroy());
-
this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
-
}
- _cleanup() {
- this._infoPage._cleanup();
+ vfunc_unroot() {
+ this._infoPage.unparent();
+ this._infoPage = null;
+
+ super.vfunc_unroot();
}
get info() {
@@ -354,11 +321,13 @@ var WeatherView = GObject.registerClass({
}
}
- _onDestroy() {
+ vfunc_unmap() {
if (this._updateId) {
this._info.disconnect(this._updateId);
this._updateId = 0;
}
+
+ super.vfunc_unmap();
}
update() {
@@ -376,8 +345,8 @@ var WeatherView = GObject.registerClass({
this._stack.visible_child_name = 'info';
}
- getInfoPage() {
- return this._infoPage;
+ getForecastStack() {
+ return this._infoPage.getForecastStack();
}
});
diff --git a/src/app/dailyForecast.js b/src/app/dailyForecast.js
index a0e3280..881a91e 100644
--- a/src/app/dailyForecast.js
+++ b/src/app/dailyForecast.js
@@ -74,32 +74,31 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
weekInfos.push(dayInfos);
day = day.add_days(1);
}
- return weekInfos;
- }
-
- update(info) {
- let forecasts = info.get_forecast_list();
- let weekInfos = this._preprocess(forecasts);
+ const temperatures = weekInfos.map(dayInfos => dayInfos.infos)
+ .flat()
+ .map(info => Util.getTemp(info));
- if (weekInfos.length > 0) {
- let weekHighestTemp = -Infinity;
- let weekLowestTemp = Infinity;
+ const weekHighestTemp = Math.max(...temperatures);
+ const weekLowestTemp = Math.min(...temperatures);
- weekInfos.map(dayInfos => dayInfos.infos).flat().forEach(info => {
- const temp = Util.getTemp(info);
+ return {
+ weekHighestTemp,
+ weekLowestTemp,
+ days: weekInfos
+ };
+ }
- weekHighestTemp = Math.max(weekHighestTemp, temp);
- weekLowestTemp = Math.min(weekLowestTemp, temp);
- });
+ update(info) {
+ let forecasts = info.get_forecast_list();
- for (let i = 0; i < weekInfos.length; i++) {
- let dayInfos = weekInfos[i];
- this._addDayEntry(dayInfos, weekHighestTemp, weekLowestTemp);
+ let forecast = this._preprocess(forecasts);
- if (i < weekInfos.length - 1)
- this._addSeparator();
- }
+ if (forecast.days.length > 1) {
+ forecast.days.reduce((_, dayInfos) => {
+ this.append(this._buildDayEntry(dayInfos, forecast.weekHighestTemp,
forecast.weekLowestTemp));
+ this.append(this._buildSeparator());
+ }, null);
} else {
let label = new Gtk.Label({
label: _('Forecast not available'),
@@ -110,136 +109,58 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
}
}
- _addDayEntry({ day, infos }, weekHighestTemp, weekLowestTemp) {
- let maxInfo;
- let maxTemp = -Infinity;
-
- let minInfo;
- let minTemp = Infinity;
-
- day = Util.getDay(day);
- let dayInfo;
- let dayDiff = Infinity;
-
- let night = Util.getNight(day);
- let nightInfo;
- let nightDiff = Infinity;
+ _buildDayEntry({ day, infos }, weekHighestTemp, weekLowestTemp) {
+ let datetime = Util.getDay(day);
- let morning = Util.getMorning(day);
- let morningInfo;
- let morningDiff = Infinity;
+ const temperatures = infos.map(info => Util.getTemp(info));
+ const minTemp = Math.min(...temperatures);
+ const maxTemp = Math.max(...temperatures);
- let afternoon = Util.getAfternoon(day);
- let afternoonInfo;
- let afternoonDiff = Infinity;
+ let periodInfos = {}, times = {
+ day: Util.getDay(datetime),
+ night: Util.getNight(datetime),
+ morning: Util.getMorning(datetime),
+ afternoon: Util.getAfternoon(datetime),
+ evening: Util.getEvening(datetime)
+ };
- let evening = Util.getEvening(day);
- let eveningInfo;
- let eveningDiff = Infinity;
-
- for (let i = 0; i < infos.length; i++) {
- let info = infos[i];
-
- let temp = Util.getTemp(info);
- if (temp > maxTemp) {
- maxInfo = info;
- maxTemp = temp;
- }
- if (temp < minTemp) {
- minInfo = info;
- minTemp = temp;
- }
-
- let datetime = Util.getDateTime(info);
-
- let diff = Math.abs(datetime.difference(day));
- if (diff < dayDiff) {
- dayInfo = info;
- dayDiff = diff;
- }
-
- diff = Math.abs(datetime.difference(night));
- if (diff < nightDiff) {
- nightInfo = info;
- nightDiff = diff;
- }
+ const datetimes = infos.map(info => Util.getDateTime(info));
- diff = Math.abs(datetime.difference(morning));
- if (diff < morningDiff) {
- morningInfo = info;
- morningDiff = diff;
- }
+ for (const period of ['day', 'night', 'morning', 'afternoon', 'evening']) {
+ const differences = datetimes.map(datetime => Math.abs(datetime.difference(times[period])));
- diff = Math.abs(datetime.difference(afternoon));
- if (diff < afternoonDiff) {
- afternoonInfo = info;
- afternoonDiff = diff;
- }
+ const index = differences.indexOf(Math.min(...differences))
- diff = Math.abs(datetime.difference(evening));
- if (diff < eveningDiff) {
- eveningInfo = info;
- eveningDiff = diff;
- }
+ periodInfos[period] = infos[index];
}
- let dayEntry = new DayEntry();
-
- let nameFormat = '%a';
- dayEntry.nameLabel.label = day.format(nameFormat);
-
- /* Translators: this is the time format for day and month name according to the current locale */
- let dateFormat = _('%b %e');
- dayEntry.dateLabel.label = day.format(dateFormat);
-
- dayEntry.image.iconName = `${dayInfo.get_icon_name()}-small`;
-
-
- dayEntry.thermometer.setTemperatureRange(weekLowestTemp, weekHighestTemp, minTemp, maxTemp);
- dayEntry.nightTemperatureLabel.label = Util.getTempString(nightInfo);
- dayEntry.nightImage.iconName = nightInfo.get_icon_name() + '-small';
- dayEntry.nightHumidity.label = nightInfo.get_humidity();
- this._setWindInfo(nightInfo, dayEntry.nightWind);
-
- dayEntry.morningTemperatureLabel.label = Util.getTempString(morningInfo);
- dayEntry.morningImage.iconName = morningInfo.get_icon_name() + '-small';
- dayEntry.morningHumidity.label = morningInfo.get_humidity();
- this._setWindInfo(morningInfo, dayEntry.morningWind);
-
- dayEntry.afternoonTemperatureLabel.label = Util.getTempString(afternoonInfo);
- dayEntry.afternoonImage.iconName = afternoonInfo.get_icon_name() + '-small';
- dayEntry.afternoonHumidity.label = afternoonInfo.get_humidity();
- this._setWindInfo(afternoonInfo, dayEntry.afternoonWind);
- dayEntry.eveningTemperatureLabel.label = Util.getTempString(eveningInfo);
- dayEntry.eveningImage.iconName = eveningInfo.get_icon_name() + '-small';
- dayEntry.eveningHumidity.label = eveningInfo.get_humidity();
- this._setWindInfo(eveningInfo, dayEntry.eveningWind);
+ const { day: dayInfo, night, morning, afternoon, evening } = periodInfos;
- this.prepend(dayEntry);
+ return new DayEntry({
+ datetime,
+ weekHighestTemp,
+ weekLowestTemp,
+ maxTemp,
+ minTemp,
+ day: dayInfo,
+ night,
+ morning,
+ afternoon,
+ evening
+ });
}
- _addSeparator() {
- let separator = new Gtk.Separator({
+ _buildSeparator() {
+ return new Gtk.Separator({
orientation: Gtk.Orientation.VERTICAL,
visible: true
});
- this.prepend(separator);
- }
-
- _setWindInfo(info, label) {
- let [ok, speed, direction] = info.get_value_wind(GWeather.SpeedUnit.DEFAULT);
- if (ok) {
- label.label = `${speed.toFixed(1).toString()}
${GWeather.speed_unit_to_string(GWeather.SpeedUnit.DEFAULT)}`;
- } else {
- /* Fall back to get_wind() */
- label.label = info.get_wind();
- }
}
clear() {
- for (const w of Array.from(this)) {
- this.remove(w);
+ for (const entry of Array.from(this)) {
+ entry.unparent();
}
}
});
@@ -259,89 +180,80 @@ var DayEntry = GObject.registerClass({
}, class DayEntry extends Gtk.Widget {
_init(params) {
- super._init(params);
-
+ const {
+ datetime,
+ maxTemp,
+ minTemp,
+ weekHighestTemp,
+ weekLowestTemp,
+ day,
+ night,
+ morning,
+ afternoon,
+ evening
+ } = params;
+
+ super._init();
+
+
+ this.datetime = datetime;
+ this.info = {
+ day,
+ night,
+ morning,
+ afternoon,
+ evening
+ };
+ this.maxTemp = maxTemp;
+ this.minTemp = minTemp;
+ this.weekHighestTemp = weekHighestTemp;
+ this.weekLowestTemp = weekLowestTemp;
this.layoutManager.orientation = Gtk.Orientation.VERTICAL;
}
- get nameLabel() {
- return this._nameLabel;
- }
-
- get dateLabel() {
- return this._dateLabel;
- }
-
- get image() {
- return this._image;
- }
-
- get thermometer() {
- return this._thermometer;
- }
-
- get nightTemperatureLabel() {
- return this._nightTemperatureLabel;
- }
-
- get nightImage() {
- return this._nightImage;
- }
+ vfunc_root() {
+ super.vfunc_root();
- get nightHumidity() {
- return this._nightHumidity;
- }
-
- get nightWind() {
- return this._nightWind;
- }
-
- get morningTemperatureLabel() {
- return this._morningTemperatureLabel;
- }
-
- get morningImage() {
- return this._morningImage;
- }
-
- get morningHumidity() {
- return this._morningHumidity;
- }
-
- get morningWind() {
- return this._morningWind;
- }
+ const { datetime } = this;
+ const { day: dayInfo, evening: eveningInfo, night: nightInfo, morning: morningInfo, afternoon:
afternoonInfo } = this.info;
- get afternoonTemperatureLabel() {
- return this._afternoonTemperatureLabel;
- }
-
- get afternoonImage() {
- return this._afternoonImage;
- }
-
- get afternoonHumidity() {
- return this._afternoonHumidity;
- }
-
- get afternoonWind() {
- return this._afternoonWind;
- }
-
- get eveningTemperatureLabel() {
- return this._eveningTemperatureLabel;
- }
-
- get eveningImage() {
- return this._eveningImage;
- }
-
- get eveningHumidity() {
- return this._eveningHumidity;
+ this._nameLabel.label = datetime.format('%a');
+ /* Translators: this is the time format for day and month name according to the current locale */
+ let dateFormat = _('%b %e');
+ this._dateLabel.label = datetime.format(dateFormat);
+
+ this._image.iconName = `${dayInfo.get_icon_name()}-small`;
+
+ this._thermometer.range = new Thermometer.TemperatureRange({ dailyLow: this.minTemp, dailyHigh:
this.maxTemp, weeklyLow: this.weekLowestTemp, weeklyHigh: this.weekHighestTemp });
+ this._nightTemperatureLabel.label = Util.getTempString(nightInfo);
+ this._nightImage.iconName = nightInfo.get_icon_name() + '-small';
+ this._nightHumidity.label = nightInfo.get_humidity();
+ this._setWindInfo(nightInfo, this._nightWind);
+
+ this._morningTemperatureLabel.label = Util.getTempString(morningInfo);
+ this._morningImage.iconName = morningInfo.get_icon_name() + '-small';
+ this._morningHumidity.label = morningInfo.get_humidity();
+ this._setWindInfo(morningInfo, this._morningWind);
+
+ this._afternoonTemperatureLabel.label = Util.getTempString(afternoonInfo);
+ this._afternoonImage.iconName = afternoonInfo.get_icon_name() + '-small';
+ this._afternoonHumidity.label = afternoonInfo.get_humidity();
+ this._setWindInfo(afternoonInfo, this._afternoonWind);
+
+ this._eveningTemperatureLabel.label = Util.getTempString(eveningInfo);
+ this._eveningImage.iconName = eveningInfo.get_icon_name() + '-small';
+ this._eveningHumidity.label = eveningInfo.get_humidity();
+ this._setWindInfo(eveningInfo, this._eveningWind);
}
- get eveningWind() {
- return this._eveningWind;
+ _setWindInfo(info, label) {
+ let [ok, speed, direction] = info.get_value_wind(GWeather.SpeedUnit.DEFAULT);
+ if (ok) {
+ label.label = `${speed.toFixed(1).toString()}
${GWeather.speed_unit_to_string(GWeather.SpeedUnit.DEFAULT)}`;
+ } else {
+ /* Fall back to get_wind() */
+ label.label = info.get_wind();
+ }
}
});
diff --git a/src/app/entry.js b/src/app/entry.js
index 4531615..5380ab1 100644
--- a/src/app/entry.js
+++ b/src/app/entry.js
@@ -43,8 +43,6 @@ const LocationListModel = GObject.registerClass(
Implements: [Gio.ListModel]
},
class LocationListModel extends Gtk.Widget {
-
-
_init() {
super._init();
@@ -299,7 +297,7 @@ var LocationSearchEntry = GObject.registerClass(
}
-
+
_populateModel() {
let filter = new Gtk.StringFilter();
this._filter = filter;
@@ -350,10 +348,10 @@ var LocationSearchEntry = GObject.registerClass(
return factory;
}
- _cleanup() {
- if (this._listview instanceof Gtk.ListView) {
- this._listview.set_model(null);
- }
+ vfunc_unroot() {
+ this._listview?.set_model(null);
+
+ super.vfunc_unroot();
}
}
);
diff --git a/src/app/hourlyForecast.js b/src/app/hourlyForecast.js
index 2284c7d..b2e32f5 100644
--- a/src/app/hourlyForecast.js
+++ b/src/app/hourlyForecast.js
@@ -218,8 +218,6 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
super.vfunc_snapshot(snapshot);
cr.$dispose();
-
- return Gdk.EVENT_PROPAGATE;
}
});
diff --git a/src/app/main.js b/src/app/main.js
index 83a4363..49f0c72 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -52,194 +52,188 @@ const ShellIntegrationInterface = ByteArray.toString(
Gio.resources_lookup_data('/org/gnome/shell/ShellWeatherIntegration.xml', 0).get_data());
function initEnvironment() {
- window.getApp = function() {
+ window.getApp = function () {
return Gio.Application.get_default();
};
}
const Application = GObject.registerClass(
- class WeatherApplication extends Gtk.Application {
+ class WeatherApplication extends Adw.Application {
- _init() {
- super._init({
- application_id: pkg.name,
- flags: (Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID | Gio.ApplicationFlags.FLAGS_NONE)
- });
- let name_prefix = '';
- if (pkg.name.endsWith('Devel')) {
- name_prefix = '(Development) ';
+ _init() {
+ super._init({
+ application_id: pkg.name,
+ flags: Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID,
+ });
+ let name_prefix = '';
+ if (pkg.name.endsWith('Devel')) {
+ name_prefix = '(Development) ';
+ }
+ GLib.set_application_name(name_prefix + _("Weather"));
+ Gtk.Window.set_default_icon_name(pkg.name);
}
- GLib.set_application_name(name_prefix + _("Weather"));
- Gtk.Window.set_default_icon_name(pkg.name);
- }
- _onQuit() {
- this.quit();
- }
-
- _onShowLocation(action, parameter) {
- let location = this.world.deserialize(parameter.deep_unpack());
- let win = this._createWindow();
+ _onQuit() {
+ this.quit();
+ }
- let info = this.model.addNewLocation(location, false);
- win.showInfo(info, false);
- this._showWindowWhenReady(win);
- }
+ _onShowLocation(action, parameter) {
+ let location = this.world.deserialize(parameter.deep_unpack());
+ let win = this._createWindow();
- _onShowSearch(action, parameter) {
- let text = parameter.deep_unpack();
- let win = this._createWindow();
+ let info = this.model.addNewLocation(location, false);
+ win.showInfo(info, false);
+ this._showWindowWhenReady(win);
+ }
- win.showSearch(text);
- this._showWindowWhenReady(win);
- }
+ _onShowSearch(action, parameter) {
+ let text = parameter.deep_unpack();
+ let win = this._createWindow();
- vfunc_startup() {
- super.vfunc_startup();
- Adw.init();
+ win.showSearch(text);
+ this._showWindowWhenReady(win);
+ }
- Util.loadStyleSheet('/org/gnome/Weather/application.css');
+ vfunc_startup() {
+ super.vfunc_startup();
- // TODO
- // Handy.StyleManager
- // .get_default()
- // .set_color_scheme(Handy.ColorScheme.PREFER_LIGHT);
+ Util.loadStyleSheet('resource:///org/gnome/Weather/application.css');
- this.world = GWeather.Location.get_world();
- this.model = new World.WorldModel(this.world, true);
- this.currentLocationController = new CurrentLocationController.CurrentLocationController(this.model);
+ this.world = GWeather.Location.get_world();
+ this.model = new World.WorldModel(this.world, true);
+ this.currentLocationController = new
CurrentLocationController.CurrentLocationController(this.model);
- this.model.load();
+ this.model.load();
- this.model.connect('notify::loading', () => {
+ this.model.connect('notify::loading', () => {
+ if (this.model.loading)
+ this.mark_busy();
+ else
+ this.unmark_busy();
+ });
if (this.model.loading)
this.mark_busy();
- else
- this.unmark_busy();
- });
- if (this.model.loading)
- this.mark_busy();
- let quitAction = new Gio.SimpleAction({
- enabled: true,
- name: 'quit'
- });
- quitAction.connect('activate', () => this._onQuit());
- this.add_action(quitAction);
-
- let showLocationAction = new Gio.SimpleAction({
- enabled: true,
- name: 'show-location',
- parameter_type: new GLib.VariantType('v'),
- });
- showLocationAction.connect('activate', (action, parameter) => {
- this._onShowLocation(action, parameter);
- });
- this.add_action(showLocationAction);
-
- let showSearchAction = new Gio.SimpleAction({
- enabled: true,
- name: 'show-search',
- parameter_type: new GLib.VariantType('v'),
- })
- showSearchAction.connect('activate', (action, parameter) => {
- this._onShowSearch(action, parameter);
- });
- this.add_action(showSearchAction);
-
- let gwSettings = new Gio.Settings({ schema_id: 'org.gnome.GWeather' });
- // we would like to use g_settings_create_action() here
- // but that does not handle correctly the case of 'default'
- // we would also like to use g_settings_bind_with_mapping(), but that
- // function is not introspectable (two callbacks, one destroy notify)
- // so we hand code the behavior we want
- function resolveDefaultTemperatureUnit(unit) {
- unit = GWeather.TemperatureUnit.to_real(unit);
- if (unit == GWeather.TemperatureUnit.CENTIGRADE)
- return new GLib.Variant('s', 'centigrade');
- else if (unit == GWeather.TemperatureUnit.FAHRENHEIT)
- return new GLib.Variant('s', 'fahrenheit');
- else
- return new GLib.Variant('s', 'default');
- }
- let temperatureAction = new Gio.SimpleAction({
- enabled: true,
- name: 'temperature-unit',
- state: resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit')),
- parameter_type: new GLib.VariantType('s')
- });
- temperatureAction.connect('activate', function(action, parameter) {
- action.change_state(parameter);
- })
- temperatureAction.connect('change-state', function(action, state) {
- gwSettings.set_value('temperature-unit', state);
- });
- gwSettings.connect('changed::temperature-unit', function() {
- temperatureAction.state = resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit'));
- });
- this.add_action(temperatureAction);
-
- this.set_accels_for_action("win.selection-mode", ["Escape"]);
- this.set_accels_for_action("win.select-all", ["<Primary>a"]);
- this.set_accels_for_action("app.quit", ["<Primary>q"]);
- }
+ let quitAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'quit'
+ });
+ quitAction.connect('activate', () => this._onQuit());
+ this.add_action(quitAction);
- vfunc_dbus_register(conn, path) {
- this._shellIntegration = new ShellIntegration();
- this._shellIntegration.export(conn, path);
- return true;
- }
+ let showLocationAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'show-location',
+ parameter_type: new GLib.VariantType('v'),
+ });
+ showLocationAction.connect('activate', (action, parameter) => {
+ this._onShowLocation(action, parameter);
+ });
+ this.add_action(showLocationAction);
+
+ let showSearchAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'show-search',
+ parameter_type: new GLib.VariantType('v'),
+ })
+ showSearchAction.connect('activate', (action, parameter) => {
+ this._onShowSearch(action, parameter);
+ });
+ this.add_action(showSearchAction);
+
+ let gwSettings = new Gio.Settings({ schema_id: 'org.gnome.GWeather' });
+ // we would like to use g_settings_create_action() here
+ // but that does not handle correctly the case of 'default'
+ // we would also like to use g_settings_bind_with_mapping(), but that
+ // function is not introspectable (two callbacks, one destroy notify)
+ // so we hand code the behavior we want
+ function resolveDefaultTemperatureUnit(unit) {
+ unit = GWeather.TemperatureUnit.to_real(unit);
+ if (unit == GWeather.TemperatureUnit.CENTIGRADE)
+ return new GLib.Variant('s', 'centigrade');
+ else if (unit == GWeather.TemperatureUnit.FAHRENHEIT)
+ return new GLib.Variant('s', 'fahrenheit');
+ else
+ return new GLib.Variant('s', 'default');
+ }
+ let temperatureAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'temperature-unit',
+ state: resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit')),
+ parameter_type: new GLib.VariantType('s')
+ });
+ temperatureAction.connect('activate', function (action, parameter) {
+ action.change_state(parameter);
+ })
+ temperatureAction.connect('change-state', function (action, state) {
+ gwSettings.set_value('temperature-unit', state);
+ });
+ gwSettings.connect('changed::temperature-unit', function () {
+ temperatureAction.state =
resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit'));
+ });
+ this.add_action(temperatureAction);
- vfunc_dbus_unregister(conn, path) {
- this._shellIntegration.unexport(conn);
- }
+ this.set_accels_for_action("win.selection-mode", ["Escape"]);
+ this.set_accels_for_action("win.select-all", ["<Primary>a"]);
+ this.set_accels_for_action("app.quit", ["<Primary>q"]);
+ }
- _createWindow() {
- return new Window.MainWindow({ application: this });
- }
+ vfunc_dbus_register(conn, path) {
+ this._shellIntegration = new ShellIntegration();
+ this._shellIntegration.export(conn, path);
+ return true;
+ }
- _showWindowWhenReady(win) {
- let notifyId;
+ vfunc_dbus_unregister(conn, path) {
+ this._shellIntegration.unexport(conn);
+ }
- if (this.model.loading) {
- let timeoutId;
- let model = this.model;
+ _createWindow() {
+ return new Window.MainWindow({ application: this });
+ }
- timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function() {
- log('Timeout during model load, perhaps the network is not available?');
- model.disconnect(notifyId);
+ _showWindowWhenReady(win) {
+ let notifyId;
+
+ if (this.model.loading) {
+ let timeoutId;
+ let model = this.model;
+
+ timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function () {
+ log('Timeout during model load, perhaps the network is not available?');
+ model.disconnect(notifyId);
+ win.show();
+ return false;
+ });
+ notifyId = this.model.connect('notify::loading', function (model) {
+ if (model.loading)
+ return;
+
+ model.disconnect(notifyId);
+ GLib.source_remove(timeoutId);
+ win.show();
+ });
+ } else {
win.show();
- return false;
- });
- notifyId = this.model.connect('notify::loading', function(model) {
- if (model.loading)
- return;
+ }
- model.disconnect(notifyId);
- GLib.source_remove(timeoutId);
- win.show();
- });
- } else {
- win.show();
+ return win;
}
- return win;
- }
-
- vfunc_activate() {
- let win = this._createWindow();
- win.showDefault();
- this._showWindowWhenReady(win);
- }
+ vfunc_activate() {
+ let win = this._createWindow();
+ win.showDefault();
+ this._showWindowWhenReady(win);
+ }
- vfunc_shutdown() {
- GWeather.Info.store_cache();
- this.model.saveSettingsNow();
+ vfunc_shutdown() {
+ GWeather.Info.store_cache();
+ this.model.saveSettingsNow();
- super.vfunc_shutdown();
- }
-});
+ super.vfunc_shutdown();
+ }
+ });
let ShellIntegration = class ShellIntegration {
constructor() {
@@ -278,7 +272,7 @@ let ShellIntegration = class ShellIntegration {
function main(argv) {
initEnvironment();
- const application = new Application();
+ let application = new Application();
application.connect("window-removed", (_, window) => {
if (window instanceof Window.MainWindow) {
@@ -287,4 +281,6 @@ function main(argv) {
});
application.run(argv);
+
+ application = null;
}
diff --git a/src/app/thermometer.js b/src/app/thermometer.js
index 59a531c..ba5f4b6 100644
--- a/src/app/thermometer.js
+++ b/src/app/thermometer.js
@@ -1,6 +1,7 @@
/* thermometer.js
*
* Copyright 2021 Vitaly Dyachkov <obyknovenius me com>
+ * Copyright 2022 Evan Welsh <contact evanwelsh com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,172 +20,176 @@
*/
const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
-const Gsk = imports.gi.Gsk;
-const Pango = imports.gi.Pango;
-const Cairo = imports.cairo;
-const Graphene = imports.gi.Graphene;
+
+const Util = imports.misc.util;
+
+var TemperatureRange = class TemperatureRange {
+ dailyLow;
+ dailyHigh;
+ weeklyLow;
+ weeklyHigh;
+
+ constructor({ dailyLow, dailyHigh, weeklyLow, weeklyHigh }) {
+ this.dailyLow = dailyLow;
+ this.dailyHigh = dailyHigh;
+ this.weeklyLow = weeklyLow;
+ this.weeklyHigh = weeklyHigh;
+ }
+}
+
+const ThermometerScaleInternal = GObject.registerClass(class extends Gtk.Widget {
+ _init() {
+ super._init({
+ vexpand: true,
+ hexpand: true,
+ cssClasses: ['inner'],
+ });
+ }
+});
const ThermometerScale = GObject.registerClass({
CssName: 'WeatherThermometerScale',
Properties: {
- 'adjustment': GObject.ParamSpec.object(
- 'adjustment',
- 'Adjustment',
- 'The GtkAdjustment that contains the current value of this thermometer object',
+ 'range': GObject.ParamSpec.jsobject(
+ 'range',
+ 'range',
+ 'The TemperatureRange instance representing this thermometer scale',
GObject.ParamFlags.READWRITE,
- Gtk.Adjustment,
),
},
}, class ThermometerScale extends Gtk.Widget {
- _init({ adjustment, radius = 12, margin = 12, ...params }) {
- super._init(params);
-
- this._adjustment = adjustment;
- this._adjustment.connect('changed', () => {
- this.queue_draw();
+ _init({ range = null, ...params }) {
+ super._init({
+ vexpand: true,
+ halign: Gtk.Align.CENTER,
+ ...params
});
- this.vexpand = true;
+ this.range = range;
- this._radius = radius;
- this._margin = margin;
+ this.inner = new ThermometerScaleInternal();
}
- vfunc_measure(orientation /*, for_size */) {
- let minimum = this._radius + this._margin, minimum_baseline = -1, natural_baseline = - 1;
-
- if (orientation === Gtk.Orientation.HORIZONTAL) {
- return [minimum, minimum, minimum_baseline, natural_baseline];
- } else {
- return [minimum, minimum, minimum_baseline, natural_baseline];
- }
+ vfunc_root() {
+ this.inner.set_parent(this);
}
- vfunc_snapshot(snapshot) {
- if (!this._adjustment)
- return super.vfunc_snapshot(snapshot);
+ vfunc_unroot() {
+ this.inner.unparent();
+ }
- const allocation = this.get_allocation();
+ vfunc_map() {
+ super.vfunc_map();
- const rect = new Graphene.Rect();
- rect.init(0, 0, allocation.width, allocation.height);
+ this._rangeChangedId = this.connect('notify::range', () => {
+ this.queue_draw();
+ });
+ }
- let cr = snapshot.append_cairo(rect);
- const lower = this._adjustment.get_lower();
- const upper = this._adjustment.get_upper();
- const value = this._adjustment.get_value();
- const pageSize = this._adjustment.get_page_size();
+ vfunc_unmap() {
+ this.disconnect(this._rangeChangedId);
- const width = this.get_allocated_width();
- const height = this.get_allocated_height();
+ super.vfunc_unmap();
+ }
- const radius = this._radius;
- const margin = this._margin;
+ vfunc_size_allocate(width, height, baseline) {
+ super.vfunc_size_allocate(width, height, baseline);
- const maxScaleHeight = height - 2 * radius - 2 * margin;
+ if (!this.range) return;
- const factor = maxScaleHeight / (upper - lower);
- const scaleY = radius + margin + (upper - value - pageSize) * factor;
- const scaleHeight = pageSize * factor;
+ const { dailyHigh, dailyLow, weeklyHigh, weeklyLow } = this.range;
- if (maxScaleHeight > 0) {
- this._renderScale(cr, width / 2 - radius, scaleY, radius, scaleHeight);
- }
+ const { top, bottom, left, right } = this.get_style_context().get_padding();
- super.vfunc_snapshot(snapshot);
+ const temperatureRange = weeklyHigh - weeklyLow;
+ const yScale = (height - top - bottom) / temperatureRange;
- cr.$dispose();
- }
+ const innerWidth = width - left - right;
+ const innerHeight = yScale * (dailyHigh - dailyLow) - bottom;
- _renderScale(cr, x, y, radius, height) {
- const gradient = this._createGradient(y - radius, y + height + radius);
- cr.setSource(gradient);
+ const x = left;
+ const y = yScale * (weeklyHigh - dailyHigh) - top;
- cr.newSubPath();
- cr.arc(x + radius, y, radius, Math.PI, 0);
- cr.arc(x + radius, y + height, radius, 0, Math.PI);
- cr.closePath();
- cr.fill();
+ this.inner.size_allocate(new Gdk.Rectangle({
+ x,
+ y,
+ width: innerWidth,
+ height: innerHeight
+ }), -1);
}
- _createGradient(start, end) {
- const pattern = new Cairo.LinearGradient(0, start, 0, end);
-
- const styleContext = this.get_style_context();
-
- const [, warmColor] = styleContext.lookup_color('thermometer_warm_color');
- pattern.addColorStopRGB(0.0, warmColor.red, warmColor.green, warmColor.blue);
+ vfunc_measure(orientation, for_size) {
+ let minimum_baseline = -1, natural_baseline = - 1;
- const [, coldColor] = styleContext.lookup_color('thermometer_cold_color');
- pattern.addColorStopRGB(1.0, coldColor.red, coldColor.green, coldColor.blue);
+ this.inner.measure(orientation, for_size);
- return pattern;
+ if (orientation === Gtk.Orientation.HORIZONTAL) {
+ return [24, 24, minimum_baseline, natural_baseline];
+ } else {
+ return [36, 48, minimum_baseline, natural_baseline];
+ }
}
-})
+});
-var Thermometer = GObject.registerClass({
+// This will be import.meta.url when converted to ESM.
+const url = Gio.File.parse_name(imports.app.entry.__file__).get_uri();
+var Thermometer = GObject.registerClass({
CssName: 'WeatherThermometer',
+ Template: GLib.Uri.resolve_relative(url, './thermometer.ui', 0),
+ InternalChildren: ['scale', 'highLabel', 'lowLabel'],
+ Properties: {
+ 'range': GObject.ParamSpec.jsobject(
+ 'range',
+ 'range',
+ 'The TemperatureRange instance representing this thermometer scale',
+ GObject.ParamFlags.READWRITE,
+ ),
+ },
}, class Thermometer extends Gtk.Widget {
-
_init({ ...params }) {
super._init(params);
- this._adjustment = Gtk.Adjustment.new(
- 0,
- 0,
- 0,
- 0,
- 0,
- 0
- );
-
this.layoutManager.orientation = Gtk.Orientation.VERTICAL;
- this._scale = new ThermometerScale({ adjustment: this._adjustment });
- this._highLabel = new Gtk.Label();
- this._highLabel.add_css_class('high');
- this._lowLabel = new Gtk.Label();
- this._lowLabel.add_css_class('low');
+ this._scale = this.get_template_child(Thermometer, 'scale');
+ this._highLabel = this.get_template_child(Thermometer, 'highLabel');
+ this._lowLabel = this.get_template_child(Thermometer, 'lowLabel');
+ }
- this._highLabel.set_parent(this);
- this._scale.set_parent(this);
- this._lowLabel.set_parent(this);
+ vfunc_root() {
+ super.vfunc_root();
- this._updateLabels();
+ this.bind_property('range', this._scale, 'range', GObject.BindingFlags.DEFAULT);
- this._radius = 12;
- this._margin = 12;
+ this.bind_property_full('range', this._lowLabel, 'label', GObject.BindingFlags.DEFAULT, range => {
+ return [!!range, Util.formatTemperature(range?.dailyLow) ?? ''];
+ }, null);
- }
+ this.bind_property_full('range', this._highLabel, 'label', GObject.BindingFlags.DEFAULT, range => {
+ return [!!range, Util.formatTemperature(range?.dailyHigh) ?? ''];
+ }, null);
- setTemperatureRange(weekLowestTemp, weekHighestTemp, minTemp, maxTemp) {
- this._adjustment.configure(
- minTemp,
- weekLowestTemp,
- weekHighestTemp,
- 0, 0,
- maxTemp - minTemp
- );
+ // Expression version
+ // const highExpression = new Gtk.ClosureExpression(String, thermometer => {
+ // return Util.formatTemperature(thermometer.range?.dailyHigh) ?? '';
+ // }, [new Gtk.PropertyExpression(this, null, 'range')]);
- this._updateLabels();
+ // const lowExpression = new Gtk.ClosureExpression(String, thermometer => {
+ // return Util.formatTemperature(thermometer.range?.dailyLow) ?? '';
+ // }, [new Gtk.PropertyExpression(this, null, 'range')]);
+ // highExpression.bind(this._highLabel, 'label', this);
+ // lowExpression.bind(this._lowLabel, 'label', this);
}
- _updateLabels() {
- if (!this._adjustment) return;
-
- const value = this._adjustment.get_value();
- const pageSize = this._adjustment.get_page_size();
-
- const highLabel = Math.round(value + pageSize) + "°";
- this._highLabel.label = highLabel;
-
- const lowLabel = Math.round(value) + "°";
- this._lowLabel.label = lowLabel;
+ vfunc_unroot() {
+ super.vfunc_unroot();
}
});
diff --git a/src/app/thermometer.ui b/src/app/thermometer.ui
new file mode 100644
index 0000000..69cd6e0
--- /dev/null
+++ b/src/app/thermometer.ui
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="Gjs_Thermometer">
+ <child>
+ <object class="GtkLabel" id="highLabel">
+ <property name="css-classes">high</property>
+ </object>
+ </child>
+ <child>
+ <object class="Gjs_ThermometerScale" id="scale">
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lowLabel">
+ <property name="css-classes">low</property>
+ </object>
+ </child>
+ </template>
+</interface>
\ No newline at end of file
diff --git a/src/app/window.js b/src/app/window.js
index 111899e..21ba1dc 100644
--- a/src/app/window.js
+++ b/src/app/window.js
@@ -36,8 +36,8 @@ const Page = {
var MainWindow = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/window.ui',
InternalChildren: ['header', 'refreshRevealer', 'refresh', 'forecastStackSwitcher', 'stack',
- 'titleStack','searchEntry', 'searchView', 'forecastStackSwitcherBar']
-}, class MainWindow extends Adw.ApplicationWindow {
+ 'titleStack', 'searchEntry', 'searchView', 'forecastStackSwitcherBar']
+}, class MainWindow extends Adw.ApplicationWindow {
_init(params) {
super._init(params);
@@ -53,13 +53,6 @@ var MainWindow = GObject.registerClass({
aboutAction.connect('activate', () => this._showAbout());
this.add_action(aboutAction);
- let closeAction = new Gio.SimpleAction({
- enabled: true,
- name: 'close'
- });
- closeAction.connect('activate', () => this._close());
- this.add_action(closeAction);
-
let refreshAction = new Gio.SimpleAction({
enabled: true,
name: 'refresh'
@@ -79,11 +72,11 @@ var MainWindow = GObject.registerClass({
this._cityView = new City.WeatherView(this.application, this,
{ hexpand: true, vexpand: true });
- this._stack.add_named(this._cityView, 'city');
- this._forecastStackSwitcher.set_stack(this._cityView.getInfoPage().getForecastStack());
+ this._stack.add_named(this._cityView, 'city');
- this._forecastStackSwitcherBar.set_stack(this._cityView.getInfoPage().getForecastStack());
+ this._forecastStackSwitcher.stack = this._cityView.getForecastStack();
+ this._forecastStackSwitcherBar.stack = this._cityView.getForecastStack();
this._stack.set_visible_child(this._searchView);
@@ -99,9 +92,11 @@ var MainWindow = GObject.registerClass({
// this.show_all();
}
- on_destroy() {
- this._cityView._cleanup();
- this._searchEntry._cleanup();
+ vfunc_unroot() {
+ this._cityView.unparent();
+ this._cityView = null;
+
+ super.vfunc_unroot();
}
update() {
@@ -225,8 +220,4 @@ var MainWindow = GObject.registerClass({
aboutDialog.show();
}
-
- _close() {
- this.destroy();
- }
});
diff --git a/src/misc/util.js b/src/misc/util.js
index e3ffadc..c15ff20 100644
--- a/src/misc/util.js
+++ b/src/misc/util.js
@@ -46,7 +46,7 @@ function loadUI(resourcePath, objects) {
function loadStyleSheet(resource) {
let provider = new Gtk.CssProvider();
- provider.load_from_file(Gio.File.new_for_uri('resource://' + resource));
+ provider.load_from_file(Gio.File.new_for_uri(resource));
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
@@ -195,11 +195,15 @@ function getTemp(info) {
return temp;
}
+function formatTemperature(value) {
+ return value ? `${Math.round(value).toFixed(0)}°` : undefined;
+};
+
function getTempString(info) {
let [ok, temp] = info.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
if (!ok)
return "--";
- return Math.round(temp) + "°";
+ return formatTemperature(temp);
}
function isDarkTheme() {
diff --git a/src/org.gnome.Weather.src.gresource.xml.in b/src/org.gnome.Weather.src.gresource.xml.in
index 220a28b..6bea654 100644
--- a/src/org.gnome.Weather.src.gresource.xml.in
+++ b/src/org.gnome.Weather.src.gresource.xml.in
@@ -5,6 +5,7 @@
<file>app/currentLocationController.js</file>
<file>app/hourlyForecast.js</file>
<file>app/thermometer.js</file>
+ <file>app/thermometer.ui</file>
<file>app/dailyForecast.js</file>
<file>app/entry.js</file>
<file>app/main.js</file>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]