Garminウォッチフェイスの作成:デジタル時計3

前作ったものを1週間ほど使ってみて、いろいろと手直しをして、使い続けてもいいかなと思ったのがこちら。

変更点は次の通り

  • 以前は右端にあった日の出、日の入りを示すゲージを円周に配置
    これにより、その部分だけ24時間計見た異なものになった。
  • 上記に合わせ、24時間計の部分を見やすく改良。
    • 現在の時刻は赤枠の黄色線にした。
    • 3,9,15,21時をわかりやすい色と線にした。
      0,6,12,18時はそれぞれ90度位置になるため、現状のままでもわかりやすいのでそのまま
    • 24時間計の描画を、中のテキストの描画の後に行う。
      テキストの描画領域は四角形で、その四角形の部分をバックグラウンド色で描画されてしまうため、描画する順番により、24時間計の線が一部欠けてしまう部分があったため。
  • 日の出、日の入り時刻を表すテキストのフォントサイズを大きめにする。

見やすいものかどうかについては、実機で確認しないと分からない部分が多く、シミューレーターで見やすくても、実機では見づらいと感じたものが結構あった。

コードは以下の通り。
一応、コメントも入れてみた。

using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.System;
using Toybox.Lang;
using Toybox.Time;
using Toybox.Position;

class DigitalView extends WatchUi.WatchFace {
    var center;			// 画面の中心座標[x,y]
    var bmBluetooth;	// bluetoothのビットマップ
    var whBluetooth;	// bluetoothビットマップの幅と高さ
    var xyBluetooth;	// bluetoothビットマップの描画のためのxy座標
    var yBluetooth;		// bluetoothの青丸描画のためのy座標
    var bmNotify;		// メールビットマップ
    var yNotify;		// メールビットマップのy座標
    
    var fnLarge;		// 大きいフォント
    var fnNormal;		// 小さいフォント
    
    var fnLargeSize;	// 大きいフォントの高さ
    var fnNormalSize;	// 小さいフォントの高さ
	var viewSecX;		// 秒の描画x座標
	
	var sinRad899;		// 日没・日の出計算用の固定値
	
	var toRad;			// 度・ラジアン変換用固定値
	
	var doViewSec = false;	// 高電力モード時に秒を描画するためのフラグ
    
    // 低電力モードに入った場合に呼び出される関数
    function onEnterSleep() {
    	doViewSec = false;
    }
    
    // 高電力モードに入った場合に呼び出される関数
    function onExitSleep() {
    	doViewSec = true;
    }
    
    function initialize() {
        WatchFace.initialize();
        
        bmBluetooth = WatchUi.loadResource(Rez.Drawables.BluetoothIcon);
		whBluetooth = [bmBluetooth.getWidth() / 2, bmBluetooth.getHeight() / 2];
        
        bmNotify = WatchUi.loadResource(Rez.Drawables.NotifyIcon);
        
        fnLarge = Graphics.FONT_NUMBER_MEDIUM;
        fnNormal = Graphics.FONT_LARGE;
        
        toRad = Math.PI / 180.0;
        sinRad899 = Math.sin(-0.899 * toRad);
    }

    // 描画情報が決定後のリソースなどの計算用
    function onLayout(dc) {
        center = [dc.getWidth()/2, dc.getHeight()/2];
        fnLargeSize = [Graphics.getFontAscent(fnLarge), dc.getFontHeight(fnLarge)];
        fnNormalSize = [Graphics.getFontAscent(fnNormal), dc.getFontHeight(fnNormal)];

		var widthLarge = dc.getTextWidthInPixels("00:00", fnLarge) + 2;
		var widthNormal = dc.getTextWidthInPixels("00", fnNormal) + 2;
		viewSecX = center[0] + (widthLarge + widthNormal) / 2.0 - widthNormal;
		
		yNotify = center[1] + fnNormalSize[1] * 0.5 - bmNotify.getHeight() / 2 + 2;
    	yBluetooth = center[1] + fnNormalSize[1] * 1.5;
		xyBluetooth = [30, yBluetooth - whBluetooth[1]];
    }

    // 時間・日付を描画する
    // 時間は24時間制。分と秒は0リーディング2桁
    // 日付は、「月/日 曜日」といった日本風なフォーマットのみとする
    function drawTime(dc, timeM, timeS) {
    	var y = center[1] - fnLargeSize[0];
    	if (doViewSec) {
	    	dc.drawText(viewSecX - 2, y,
	    		fnLarge, timeS.hour + ":" + timeS.min.format("%02d"), Graphics.TEXT_JUSTIFY_RIGHT);
	    	dc.drawText(viewSecX + 2, center[1] - fnNormalSize[0],
	    		fnNormal, timeS.sec.format("%02d"), Graphics.TEXT_JUSTIFY_LEFT);
    	}
    	else {
	    	dc.drawText(center[0], y,
	    		fnLarge, timeS.hour + ":" + timeS.min.format("%02d"), Graphics.TEXT_JUSTIFY_CENTER);
    	}
    	dc.drawText(center[0], y - fnNormalSize[0],
    		fnNormal, timeS.month + "/" + timeS.day + " " + timeM.day_of_week,
    		Graphics.TEXT_JUSTIFY_CENTER);
    }

	// 心拍数を求める
	// 心拍が求められない場合は---を返す
    private function retrieveHeartrateText() {
		var currentHeartrate = ActivityMonitor.getHeartRateHistory(1, true).next().heartRate;
		if (currentHeartrate != ActivityMonitor.INVALID_HR_SAMPLE) {
			return currentHeartrate.format("%d");
		}
		else {
			return "---";
		}
    }    
    
    // 歩数・心拍数などの情報を表示
    function drawInformation(dc) {
        var info = ActivityMonitor.getInfo();
        // 歩数
    	dc.drawText(center[0], center[1],
    		fnNormal, info.stepGoal.format("%5d") + "/" + info.steps.format("%5d"),
    		Graphics.TEXT_JUSTIFY_CENTER);
		// 心拍数
    	dc.drawText(center[0] - 5, center[1] + fnNormalSize[1],
    		fnNormal, retrieveHeartrateText(),
    		Graphics.TEXT_JUSTIFY_RIGHT);
    }
    
    // Bluetoothとイベント通知アイコンの表示
    function drawNotify(dc) {
    	var info = System.getDeviceSettings();
    	if (info.notificationCount > 0) {
    		// メールアイコンの表示
    		dc.drawBitmap(14, yNotify, bmNotify);
    	}
    	if (info.phoneConnected) {
    		// BLEアイコンの表示
    		// 青い丸はビットマップでの描画ではなく楕円を描画している
    		dc.setColor(Graphics.COLOR_DK_BLUE, Graphics.COLOR_BLACK);
    		dc.fillEllipse(xyBluetooth[0] + 12, yBluetooth, whBluetooth[0] - 1, whBluetooth[1] - 1);
    		dc.drawBitmap(xyBluetooth[0], xyBluetooth[1], bmBluetooth);
    	}
    }

    // 時計回りで円弧を描画する
    // 角度はDC::drawArcに習い度
    function drawArc(dc, centerXY, radius, sAngle, eAngle) {
    	if (sAngle < eAngle) {
    		sAngle += 360;
    	}
    	var angle = eAngle * toRad;
    	var stepAngle = 12.0 * toRad;
    	var toAngle = sAngle * toRad;
    	var spos = [centerXY[0] + radius * Math.cos(angle), centerXY[1] - radius * Math.sin(angle)];
    	for (; angle < toAngle; angle += stepAngle) {
    		var epos = [centerXY[0] + radius * Math.cos(angle), centerXY[1] - radius * Math.sin(angle)];
    		dc.drawLine(spos[0], spos[1], epos[0], epos[1]);
    		spos = epos;
    	}
    	dc.drawLine(spos[0], spos[1], centerXY[0] + radius * Math.cos(toAngle), centerXY[1] - radius * Math.sin(toAngle));
    }
	
	// 日の出時刻を求める関数、返却は[日の出,日の入り]で、それぞれ00:00からの通算分が入る
	// 閏年は考慮せず、また1/1からの通算日計算もアバウトにしているので、その分精度は下がる(一応今のところ1~2分位の誤差か?)
	function timeSunSetRise(xy, n) {
		var y = xy[0] * toRad;
		// 日付をラジアンに変換
		var w = (((((n.month - 1) * 30.416 + n.day - 1).toLong()) % 365) + 0.5) * 2.0 * Math.PI / 365.0;
		
		// 近似式で太陽赤緯を求める
		var d = ( + 0.33281
		    - 22.984 * Math.cos(w) - 0.34990 * Math.cos(2.0 * w) - 0.13980 * Math.cos(3.0 * w)
		    + 3.7872 * Math.sin(w) + 0.03250 * Math.sin(2.0 * w) + 0.07187 * Math.sin(3.0 * w)) * toRad;

	    // 近似式で均時差を求める
		var e = + 0.0072 * Math.cos(w) - 0.0528 * Math.cos(2.0 * w) - 0.0012 * Math.cos(3.0 * w)
		    - 0.1229 * Math.sin(w) - 0.1565 * Math.sin(2.0 * w) - 0.0041 * Math.sin(3.0 * w);
		    
		// 太陽の時角幅を求める(視半径、大気差などを補正 (-0.899度) )
		var t = (Math.acos( (sinRad899 - Math.sin(d) * Math.sin(y)) / (Math.cos(d) * Math.cos(y)))) / toRad;
		// 日の出, 日の入り時刻を返す
		var c = xy[1] - 315.0;
		return [
			(((-t - c) / 15.0 - e) * 60).toLong(),
			((( t - c) / 15.0 - e) * 60).toLong()];
	}

    // 日の出・日の入りに関する情報を表示する
    // ここで取り扱う時間は00:00からの通算分としている
    // 時計枠(丸)を24時間の時間のみを表すアナログ計とする。そこに日が出ていない時間を白の円弧で描画する。
    // 時間を表すマークは緑の棒で描画。45度にある時間、3,9,15,21時は太い白で描画し時間の区切りが分かるようにする。
    function drawSun(dc, time) {        
        var info = Position.getInfo();
        if (info != null && info has :position) {
        	var work = info.position.toDegrees();
        	var sunTime = timeSunSetRise(work, time);
	        var now = time.hour * 60 + time.min;
	        
	        // 次の日の出・日の入りの時刻を表示
	        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
	        var str = "";
	        if (now < sunTime[0] || sunTime[1] <= now) {
	        	// 今は夜なので次の日の出を表示(にしたいが今は今日の日の出になっている)
	        	if (sunTime[1] <= now) {
	        		// 現在時刻が日没後0:00前なので翌日の情報を用意する
		        	time.day++;
		        	var sunTime1 = timeSunSetRise(work, time);
		        	sunTime[0] = sunTime1[0];
	        	}
	        	str = (sunTime[0] / 60).format("%2d") + ":" + (sunTime[0] % 60).format("%2d");
	        }
	        else {
	        	// 今は日中なので日没時刻を表示
	        	str = (sunTime[1] / 60).format("%2d") + ":" + (sunTime[1] % 60).format("%2d");
	        }
	    	dc.drawText(center[0] + 10, center[1] + fnNormalSize[1],
	    		fnNormal, str, Graphics.TEXT_JUSTIFY_LEFT);
	    		        	
	        // 日が出ていない時間を白の円弧で表示	        
	        var radius = center[0] - 1;
	        dc.setPenWidth(3);
	        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
	        drawArc(dc, center, radius, 270 - 0.25 * sunTime[1], 270 - 0.25 * sunTime[0]);
			// 1時間単位の線を描画
	        dc.setColor(Graphics.COLOR_GREEN, Graphics.COLOR_BLACK);
	        radius = center[0] - 8;
	        var radius2 = center[0] - 10;
	        for (var i = 0; i < 360; i += 15) {
				var arrowAngle = i * toRad;
	        	var vSin = Math.sin(arrowAngle);
	        	var vCos = Math.cos(arrowAngle);
	        	if (i % 90 == 45) {
			        dc.setPenWidth(5);
			        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
					dc.drawLine(
						center[0] + vCos * center[0],
						center[1] - vSin * center[0],
						center[0] + vCos * radius2,
						center[1] - vSin * radius2);
			        dc.setPenWidth(3);
			        dc.setColor(Graphics.COLOR_GREEN, Graphics.COLOR_BLACK);
	        	}
	        	else {
					dc.drawLine(
						center[0] + vCos * center[0],
						center[1] - vSin * center[0],
						center[0] + vCos * radius,
						center[1] - vSin * radius);
	        	}
	        }
	        // 現在時刻を示す針を表示
			var arrowAngle = (270 - 0.25 * now) * toRad;
			radius = center[0] - 10;
        	var vSin = Math.sin(arrowAngle);
        	var vCos = Math.cos(arrowAngle);
	        dc.setPenWidth(6);
	        dc.setColor(Graphics.COLOR_PINK, Graphics.COLOR_BLACK);
			dc.drawLine(
				center[0] + vCos * center[0],
				center[1] - vSin * center[0],
				center[0] + vCos * radius,
				center[1] - vSin * radius);
	        dc.setPenWidth(4);
	        dc.setColor(Graphics.COLOR_YELLOW, Graphics.COLOR_BLACK);
			dc.drawLine(
				center[0] + vCos * center[0],
				center[1] - vSin * center[0],
				center[0] + vCos * radius,
				center[1] - vSin * radius);
	        dc.setPenWidth(1);
		}
    }
    
    // バッテリー情報を表示
    function drawBattery(dc) {
    	var status = System.getSystemStats();
        dc.setColor(Graphics.COLOR_GREEN, Graphics.COLOR_BLACK);
    	dc.drawText(center[0], dc.getHeight() - fnNormalSize[1] - 8,
    		fnNormal, status.battery.format("%3.0f"),
    		Graphics.TEXT_JUSTIFY_CENTER);
    }

    // Update the view
    function onUpdate(dc) {
        dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
    	dc.clear();
    	var now = Time.now();
        var nowM = Time.Gregorian.info(now, Time.FORMAT_MEDIUM);
        var nowS = Time.Gregorian.info(now, Time.FORMAT_SHORT);
        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
        drawTime(dc, nowM, nowS);        
        drawInformation(dc);
        drawNotify(dc);		// 色の変更有
        drawBattery(dc);	// 色の変更有
    	drawSun(dc, nowS);	// 色・幅の変更有
    }

    function onPartialUpdate( dc ) {
    	// 計測したい処理を実行する
    	onUpdate(dc);
    }
}

コメント

タイトルとURLをコピーしました