Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect folding #2123

Open
tsokano opened this issue Dec 27, 2024 · 0 comments
Open

Incorrect folding #2123

tsokano opened this issue Dec 27, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@tsokano
Copy link

tsokano commented Dec 27, 2024

Zig Version

0.14.0-dev.2568+42dac40b3

ZLS Version

0.14.0-dev.322+e50d59e

Client / Code Editor / Extensions

VSCode
Version: 1.96.2
Commit: fabdb6a30b49f79a7aba0f2ad9df9b399473380f
Date: 2024-12-19T10:22:47.216Z
Electron: 32.2.6
ElectronBuildId: 10629634
Chromium: 128.0.6613.186
Node.js: 20.18.1
V8: 12.8.374.38-electron.0
OS: Linux x64 6.12.6-arch1-1

ziglang.vscode-zig 0.6.3

Steps to Reproduce and Observed Behavior

範囲を選択_001
範囲を選択_003

const std = @import("std");
const eql = std.mem.eql;
const bufPrint = std.fmt.bufPrint;
const account = @import("object_account.zig");
const bar = @import("../bar.zig");
const Period = bar.Period;
const gui = @import("deps/gui/gui.zig");
const Window = gui.Window;
const Color = gui.Color;
const TextStyle = gui.TextStyle;
const CrossHair = @import("object_crosshair.zig");
const horizontalline = @import("object_horizontalline.zig");
const trendchannel = @import("object_trendchannel.zig");
const overlay = @import("object_overlay.zig");

pub const ChartView = @This();

base_: gui.ViewBase,
_rate_axis_width_: f32 = 0,  // percentから計算する
_date_axis_height_: f32 = 0, // rate_widthから計算する
_rect_chart: gui.Rect,  // OHLC描画エリア
_rect_rate: gui.Rect,   // プライス描画エリア
_rect_date: gui.Rect,   // 日時描画エリア
_origin_x_slide: f32 = 0,       // right_margin |<- r_chart.rigtht() -> 無限大
_bar_width_: f32 = 0,           // 一本の幅
_right_bar_index: i32 = 0,      // rangeの右端 描画領域のOHLC番号 負はあり得る?
_left_bar_index: i32 = 0,       // rangeの左端 描画領域のOHLC番号 負はあり得る?
_origin_sub_time: ?bar.MTime = null, // period拡大縮小基準バー時刻 origin_xを移動させる
_origin_sub_x: f32 = 0,         // period拡大縮小基準位置 r_chart.right()からの距離
_origin_sub_out: bool = false,  // seriesの範囲外にきちゃう
_v_scale: f32 = 0,              // 縦縮尺 実値(プライスの差額)に何倍を掛ければChartの⊿Yになるか
_area_price_highest: f64 = 0,   // 表示バー中の最高値
_area_price_lowest: f64 = 0,    // 表示バー中の最安値
_area_price_center: f64 = 0,    // 描画を中央中心にするために
_pitchMajor: f64 = 0,           // 
_pitchMinor: f64 = 0,		    //
_rate_digits: u3 = 4,           // 値軸の少数点以下桁数
_fixed_v_scale: f32 = 0,        // 他のChartと比較する用の固定スケール
_use_max_v_scale: bool = false, // ObjOverlaysのMaxVScaleを使う
_series_: ?*bar.OHLCSeries = null, // 本体
_bar_type: BarType,
_reverse: bool = false, // 天地反転
_style: Style = Style{},
mouse_mode: MouseMode = .default,
// position は常にリアルタイム表示
obj_account: account.ObjAccount,
is_show_trades: bool = false,
// objects こっちにobject_editorを持ってくるとui.MouseEventが入ってしまうからダメ
// context_menu: []menu.MenuItem,
is_show_objects: bool = false,
_temp_crosshair: CrossHair = .{},
obj_horizontal_lines: horizontalline.ObjHorizontalLine,
obj_trend_channels: trendchannel.ObjTrendChannel,
is_show_overlay: bool = false,
obj_overlays: overlay.ObjOverlays, // datetimeは_series_に合わせる
// obj_virtical_lines: []VerticalLine,
// obj_temp_vertical_line: VerticalLine,
// obj_zigzags: []ZigZag,
// obj_temp_zigzag: ZigZag,
// is_show_memos: bool,
// memos: []&ObjMemo,
// memo_editor: MemoEditor = MemoEditor{},
// context_menu: ,

pub const BarType = enum{ ohlc, line, bar_ };

pub const Style = struct{
    base: gui.Style = .{},
    // draw用
    // // color_text_white    gx.Color = Color.rgb(76, 76, 76)
    // txt_cfg             gx.TextCfg = gx.TextCfg{}
    // cfg_no_data         gx.TextCfg = gx.TextCfg{
    //   size: 20
    //   color: gx.white
    //   align: .center
    //   vertical_align: .middle
    // }
    bg_color: Color = Color.rgb(5, 5, 5), //背景
    bar_color: Color = Color.rgba(43, 255, 54, 255), //43 255 54
    bar_color_body_up: Color = Color.rgb(5, 5, 5), // 陽線実体部
    bar_color_body_down: Color = Color.rgba(255, 255, 255, 205), //
    
    // grid
    // color_grid_rate_major = $if android { Color.rgb(51, 51, 51) } $else { Color.rgb(42, 42, 42) } // 太線
    // color_grid_rate_minor = $if android { Color.rgb(73, 73, 73) } $else { Color.rgb(70, 70, 70) }
    // color_grid_date_major = $if android { Color.rgb(58, 58, 58) } $else { Color.rgb(45, 45, 45) }
    // color_grid_date_minor = $if android { Color.rgb(79, 79, 79) } $else { Color.rgb(76, 76, 76) }
    color_grid_rate_major: Color = Color.rgb(42, 42, 42), // 太線
    color_grid_rate_minor: Color = Color.rgb(70, 70, 70), // 細線
    color_grid_date_major: Color = Color.rgb(45, 45, 45),
    color_grid_date_minor: Color = Color.rgb(76, 76, 76),
    fntRate: TextStyle = .{
        .size = 13,
        .color =  Color.rgb(240, 240, 240),
        .v_align = .center
    },
    fntDate: TextStyle = .{
        .size = 13,
        .color = Color.rgba(255, 255, 255, 255),
        .h_align = .center,
        .v_align = .top,
    },
    
    // // トレンドライン/水平ライン
    // obj_line_color = $if android { Color.rgba(0, 100, 255, 200) } $else { Color.rgba(0, 50, 255, 200) }
    // //
    // tmp_zigzag_color gx.Color = Color.rgba(231, 70, 70, 190)
};

pub const Param = struct {
    base: gui.ViewBase.Param = .{
        .width = 300,
        .height = 200,
        .width_min = 150,
        .height_min = 100,
    },
    id: ?[]const u8 = null,
    style: Style = .{},
    bar_type: BarType = .ohlc,
};

pub fn init(p: Param) !gui.IView {
    const self = try Window.allocator_.create(ChartView);
    try Window.putNamedView(p.id, self);
    self.* = ChartView{
        .base_ = gui.ViewBase.init(p.base),
        ._style = p.style,
        ._bar_type = p.bar_type,
        ._rect_chart = gui.Rect.init(0, 0, 0, 0),
        ._rect_rate = gui.Rect.init(0, 0, 0, 0),
        ._rect_date = gui.Rect.init(0, 0, 0, 0),
        ._bar_width_ = 4,
        .obj_overlays = overlay.ObjOverlays.init(Window.allocator_),
        .obj_account = .{},
        .obj_horizontal_lines = horizontalline.ObjHorizontalLine.init(Window.allocator_),
        .obj_trend_channels = trendchannel.ObjTrendChannel.init(Window.allocator_),
    };
    return .{
        .impl = @alignCast(@ptrCast(self)),
        .type_name = @typeName(@This()),
        .setPosFn = setPos, // self.base_.setPosFn,
        .setSizeFn = setSize, // self.base_.setSizeFn,
        // .getRectFn = self.base_.getRectFn,
        .getRectFn = getRect,
        .setPaddingFn = self.base_.setPaddingFn,
        .getContentRectFn = self.base_.getContentRectFn,
        .setVisibilityFn = self.base_.setVisibilytyFn,
        .getVisibilityFn = self.base_.getVisibilytyFn,
        .isVisibleFn = self.base_.isVisibleFn,
        .setReadonlyFn = self.base_.setReadonlyFn,
        .isReadonlyFn = self.base_.isReadonlyFn,
        // .pointInsideFn = self.base_.pointInsideFn,
        .layoutFn = self.base_.layoutFn,
        .deinitFn = deinit,
        .drawFn = draw,
        .onEventFn = onEvent,
    };
}

/// series は参照なので、元の方が解放する責任を持つ
pub fn deinit(impl: *anyopaque) void{
    // std.log.debug("ChartView.deinit()\n", .{});
    var self: *ChartView = @alignCast(@ptrCast(impl));
    self.obj_horizontal_lines.deinit();
    self.obj_trend_channels.deinit();
    self.obj_overlays.deinit();
    // if (self._series_) |sr| sr.deinit(); // ただ、これ、元の場所でもfree()するとダブルフリーになる
    // std.log.debug(.info, "ChartView.deinit() called.\n",.{});
    Window.allocator_.destroy(self);
}

//----------------

pub fn isNoData(self: ChartView) bool {
    return self._series_ == null or self._series_.size() < 1;
}

/// series を解放する責任はChartViewは持たないので、呼び出し側で管理すること
pub fn setSeries(self: *ChartView, series: ?*bar.OHLCSeries) void{
    self._series_ = series;
    self.setRatePitch();
    self._origin_x_slide = 0; // periodを変えたときは位置はリセットしたいから
    try self.moveToOriginSub();
    self.setWindowRange(); // setWindowRange()はseries[n]の設定なのでここでも必要
    // TODO Symbolごとにオブジェクトを切り替える
    // self.obj_horizontal_lines
    // self.obj_trend_channels
    // self.obj_overlays
}

/// 基準線に合わせる origin_sub_x の位置を同じ場所になるようにslideを動かす
fn moveToOriginSub(self: *ChartView) !void{
    if (self._origin_sub_time == null) return;
    // n := self.barN(self._origin_sub_time) or {
    //   // 基準線の時刻のBarが見つからない=足りない場合
    //   self._origin_sub_out = true
    //   if err !is pr.OHLCSeriesError {
    //     panic("${err.type_name()} is not handled.")
    //   }
    //   switch unsafe{ pr.OHLCSeriesErrorCode(err.code()) } {
    //     .lack_older { // 後ろなら目一杯後ろに移動させる
    //       x := self.getX(self._series_.size())
    //       dx := self._origin_sub_x - x
    //       self._origin_x_slide += dx
    //     }
    //     .lack_newer { // TODO
    //     }
    //     else {
    //       panic("$err: This should never happen.")
    //     }
    //   }
    //   return err
    // }
    // self._origin_sub_out = false
    // dx := self._origin_sub_x - self.getX(n)
    // self._origin_x_slide += dx
}

// scale & origin ----------------------------

const rate_axis_pct: f32 = 0.08; // デバイス画面幅を基準とし全てのサイズを決定していく。
const height_oscillator_pct: f32 = 0.20; // 占める比率%
const v_margin: f32 = 5; // 上下端から最高値安値までの余白
const min_rate_axis_width: f32 = 35;
const right_margin: f32 = 4; // バーの右端の余白幅 TODO 足2本分にする

pub fn setPos(impl: *anyopaque, x: f32, y: f32) void{
    // std.log.debug("ChartView.setPos({}, {})\n", .{x, y});
    var self: *ChartView = @alignCast(@ptrCast(impl));
    self.base_.rect_.setPos(x, y);
    // std.log.debug("{d}\n",.{self.base_.rect_.y});
    self.initRects();
}

pub fn setSize(impl: *anyopaque, width: f32, height: f32) void{
    // std.log.debug("ChartView.setSize({}, {})\n", .{width, height});
    var self: *ChartView = @alignCast(@ptrCast(impl));
    self.base_.rect_.setSize(width, height);
    self.initRects();
    self.setWindowRange(); // 右側位置は変わらず、左側だけ広く/狭くなる
    self.setSubOrigin(self._origin_sub_time);
}

// Layoutで必要になる
pub fn getRect(impl: *anyopaque) gui.Rect {
    var self: *ChartView = @alignCast(@ptrCast(impl));
    return self.base_.rect_.getRect();
}

/// TODO Widgetサイズから計算 TODO resizableに
fn initRects(self: *ChartView) void{
    self._rate_axis_width_ = min_rate_axis_width;
    self._date_axis_height_ = self._rate_axis_width_ / 3;

    // ローソク領域
    self._rect_chart = gui.Rect.init(
        self.base_.rect_._x_,
        self.base_.rect_._y_,
        self.base_.rect_.width() - self._rate_axis_width_,
        self.base_.rect_.height() - self._date_axis_height_);
    // std.log.debug("ChartView._rect_chart: {}\n", .{self._rect_chart});
    // プライス領域
    self._rect_rate = gui.Rect.init(
        self._rect_chart.right(),
        self._rect_chart.top(),
        self._rate_axis_width_,
        self._rect_chart.height());
    // std.log.debug("ChartView._rect_rate: {}\n", .{self._rect_rate});
    // 日付領域
    self._rect_date = gui.Rect.init(
        self._rect_chart.left(),
        self._rect_chart.bottom(),
        self._rect_chart.width(),
        self._date_axis_height_);
    // std.log.debug("ChartView._rect_date: {}\n", .{self._rect_date});
}

//----------------------------------------------

// 仕様
// originX
//   series[0]とイコールの場所 x
// 原点(originX)を動かすの
//   series初期化時 setSeries()
//   move_mouse() scroll()
// sub_origin_x
// 描画範囲
//   r_chartの右端から左端まで
//     それ以外は描画しない
//   setWindowRange()
//     series[n]を表す
//     right_bar_index = series[n]
//     left_bar_indes = series[n + 描画範囲の本数]

/// この描画原点を基準にしてBarを配置、Scrollはこれを動かして描画範囲が変える
inline fn originX(self: ChartView) f32 {
    return self._rect_chart.right() - right_margin + self._origin_x_slide;
}

/// 原点を移動させる
// TODO 過去足追加機能
// TODO スクロール時右端不十分
pub fn offsetOrigin(self: *ChartView, dx: f32) void{
    const xw = self._origin_sub_x - self._origin_x_slide;
    self._origin_x_slide += dx;
    if (dx > 0) { // 過去、左端
        const ss = if (self._series_) |sr| sr.size() else 0;
        if (self.originX() - (self._bar_width_ * @as(f32, @floatFromInt(ss))) > 0) {
            self._origin_x_slide -= dx;
        }
    } else if (dx < 0) { // 未来、右端 TODO スクロール時
        const right = self._rect_chart.right() - right_margin;
        if (right > self.originX()) { // 右端よりさらに右に原点が位置する場合は
            self._origin_x_slide -= dx;
        }
    }
    if (self._origin_sub_time != null) {
        self._origin_sub_x = self._origin_x_slide + xw;
    }
    self.setWindowRange();
}

/// 基準描画位置移動 指定されたx位置にそのtimeを持っていく
pub fn setSubOrigin(self: *ChartView, time: ?bar.MTime) void{
    self._origin_sub_time = time;
    // if (self._origin_sub_time == null) {
    //   self._origin_sub_x = 0;
    // } else {
    //   const n = self.barN(self._origin_sub_time) or -1;
    //   self._origin_sub_x = self.getX(n);
    // }
    // // println("${@FN}: origin_sub_x=${self._origin_sub_x}")
    if (self._origin_sub_time) |ost| {
        var n: i32 = -1;
        if (self.barN(ost)) |val| { n = @intCast(val); } else |_| {}
        self._origin_sub_x = self.getX(n);
    } else {
        self._origin_sub_x = 0;
    }
}

pub fn clearSubOrigin(self: *ChartView) void{
    self._origin_sub_time = null;
    self._origin_sub_x = 0;
}

/// 直近が右端にあるか
inline fn isRight(self: ChartView) bool {
    return self.originX() == (self._rect_chart.right() - right_margin);
}

/// 描画範囲の分割数 TODO 端末ごとの差異をどう吸収するか?
inline fn areaNum(self: ChartView) i32 {
    return @intFromFloat(self._rect_chart.right() / self._bar_width_);
}

/// この範囲のOHLCインデックスを描画する
inline fn setWindowRange(self: *ChartView) void{
    // 原点の位置からチャートの右端までの距離を本数で表す
    self._right_bar_index = @as(i32, @intFromFloat(((self.originX() - self._rect_chart.right()) / self._bar_width_)));
    self._left_bar_index = self._right_bar_index - @as(i32, @intFromFloat((right_margin / self._bar_width_))) + self.areaNum();
    // forループ用だから+1
    // self._range = range(self._right_bar_index, self._left_bar_index + 1);
    // println("${self.originX()} ${self._left_bar_index} - ${self._right_bar_index}")
    self.calcVScale(); // ここだとtempbarが新しくなったときに検出できない
}

//----------------------------------------------

// TODO draw()内で毎回setVScale()をやるのは無駄なので
fn needRescale(self: ChartView) bool {
    _=self;
    // highest-lowest が変わっていない
    //  表示範囲のbarが変わっていない
    //  bidaskが変わっていない
    return true;
}

// TODO Ask
fn calcVScale1(self: *ChartView) void{
    const sr = self._series_ orelse return;
    self._area_price_highest = std.math.f64_min; // TODO bidask
    self._area_price_lowest = std.math.f64_max;
    var i: i32 = self._right_bar_index;
    while (i <= self._left_bar_index) : (i += 1) {
        // std.log.debug("{}\n", .{i});
        // if (i == self._left_bar_index + 1) break;
        if (i < 0 or i >= sr.size()) continue;
        const b = sr.get(@as(usize, @intCast(i))) catch continue;
        if (b.isZero()) continue;
        if (b.high > self._area_price_highest)
            self._area_price_highest = b.high;
        if (b.low < self._area_price_lowest)
            self._area_price_lowest = b.low;
    }
    // std.log.debug("{d:.5}, {d:.5}\n", .{self._area_price_highest, self._area_price_lowest});
    //直近表示している場合だけ
    if (self.isRight()) {
        if (sr.bidask) |ba| {
            if (!ba.isNull()) {
                // TODO Askはをのバーが終わるまでは最高値で固定にしたい
                if (ba.ask_ > self._area_price_highest)
                    self._area_price_highest = ba.ask_;
                if (ba.bid_ < self._area_price_lowest)
                    self._area_price_lowest = ba.bid_;
            }
        }
    }
    self._area_price_center = (self._area_price_lowest + self._area_price_highest) / 2;
    const height = self._rect_chart.height() - (v_margin * 2);
    self._v_scale = @floatCast(height / (self._area_price_highest - self._area_price_lowest));
    // std.log.debug("ChartView.height()={d:.5}, ._v_scale={d:.5}\n", .{height, self._v_scale});

    // 指定スケール有り
    if (self._fixed_v_scale != 0) {
        self._v_scale = self._fixed_v_scale;
        return;
    }

    // // TODO overlaysのスケールも加味して決める
    // if (self._use_max_v_scale) {
    //   self._v_scale = self.obj_overlays.getMaxVScale();
    //   return;
    // }
    
    // メインのSeriesのVScaleをoverlayのscaleにセットする
    for (self.obj_overlays.list.items) |*ol| {
        ol.setVScale(self._right_bar_index, self._left_bar_index,
            height, self._area_price_highest, self._area_price_lowest);
    }
}

fn calcVScale(self: *ChartView) void{
    // 指定スケール有り
    if (self._fixed_v_scale != 0) {
        self._v_scale = self._fixed_v_scale;
        return;
    } 
    
    const sr = self._series_ orelse return;
    self._area_price_highest = std.math.floatMin(f64); // TODO bidask
    self._area_price_lowest = std.math.floatMax(f64);
    var i: i32 = self._right_bar_index;
    while (i <= self._left_bar_index) : (i += 1) {
        // std.log.debug("{}\n", .{i});
        // if (i == self._left_bar_index + 1) break;
        if (i < 0 or i >= sr.size()) continue;
        const b = sr.get(@as(usize, @intCast(i))) catch continue;
        if (b.isZero()) continue;
        if (b.high > self._area_price_highest)
            self._area_price_highest = b.high;
        if (b.low < self._area_price_lowest)
            self._area_price_lowest = b.low;
    }
    // std.log.debug("{d:.5}, {d:.5}\n", .{self._area_price_highest, self._area_price_lowest});
    //直近表示している場合だけ
    if (self.isRight()) {
        if (sr.bidask) |ba| {
            if (!ba.isNull()) {
                // TODO Askはをのバーが終わるまでは最高値で固定にしたい
                if (ba.ask_ > self._area_price_highest)
                    self._area_price_highest = ba.ask_;
                if (ba.bid_ < self._area_price_lowest)
                    self._area_price_lowest = ba.bid_;
            }
        }
    }
    self._area_price_center = (self._area_price_lowest + self._area_price_highest) / 2;
    const height = self._rect_chart.height() - (v_margin * 2);
    self._v_scale = @floatCast(height / (self._area_price_highest - self._area_price_lowest));
    // std.log.debug("ChartView.height()={d:.5}, ._v_scale={d:.5}\n", .{height, self._v_scale});

    // TODO overlaysのスケールも加味して決める
    if (self._use_max_v_scale) {
        self.obj_overlays.calcVScales(self._right_bar_index, self._left_bar_index, height);
        const max = self.obj_overlays.getMaxVScale();
        if (max > self._v_scale) self._v_scale = max;
        return;
    }

    // overlayのscaleに、メインのSeriesのVScaleをセットする
    for (self.obj_overlays.list.items) |*ol| {
        ol.setVScale(self._right_bar_index, self._left_bar_index,
            height, self._area_price_highest, self._area_price_lowest);
    }
}

//----------------------------------------------

/// _series中本目 FIXME 週足だとバグ
pub inline fn barN(self: ChartView, dt: bar.MTime) !usize {
    return self._series_.?.binarySearch(dt);
}

/// _rect_chart内
pub inline fn getX(self: ChartView, bar_index: i32) f32 {
    return self.originX() - (@as(f32, @floatFromInt(bar_index)) * self._bar_width_) - self._bar_width_;
}

/// _rect_chart内
/// スケール可変のため、真ん中を基準にするよう
pub inline fn getY(self: ChartView, price: f64) f32 {
    const vm = self._rect_chart.verticalMid();
    const pac: f32 = @floatCast((self._area_price_center - price) * self._v_scale);
    return if (!self._reverse) vm + pac else vm - pac;
}

// draw -----------------------------------------

/// メイン描画
pub fn draw(impl: *anyopaque) void {
    var self: *ChartView = @alignCast(@ptrCast(impl));
    self.base_.drawBackground(Color.rgb(5, 5, 5));
    if (self._series_ == null or self._series_.?.size() < 1) {
        self.drawNone();
    } else {
        // std.log.debug("{}\n", .{self.draw_test_num});
        // self.draw_test_num += 1;
        
        // on_new_bar で検出する術がないので、ここに置いてる
        if (self.needRescale()) self.calcVScale();
        // std.log.debug("base.rect_: {}\n", .{self.base_.rect_});
        Window.clipOn(self.base_.rect_);
        
        self.drawDateGrid();
        self.drawRateGrid();

        // std.log.debug("_rect_chart: {}\n", .{self._rect_chart});
        Window.clipOn(self._rect_chart);

        // self.drawSubOrigin()
        
        if (self.is_show_objects) {
        //   self.draw_obj_zigzag()
            self.obj_trend_channels.draw(self);
            self.obj_horizontal_lines.draw(self);
        //   self.draw_obj_v_line()
        }
        self.obj_trend_channels.drawTemp(self);
        self.obj_horizontal_lines.drawTemp(self);
        // self.draw_obj_temp_v_line()
        // self.draw_obj_temp_zigzag()
        self._temp_crosshair.draw(self) catch |err| { std.log.err("{}", .{err}); };
        
        self.drawBidAsk();
        self.drawOhlc();

        if (self.is_show_overlay) self.obj_overlays.draw(self.*);

        // if self.is_show_memos self.draw_memo()

        self.obj_account.drawOrders(self);
        if (self.is_show_trades) self.obj_account.drawTrades(self);

        self.drawTitle();

        Window.clipOff();
    }
    self.drawFrame();
}

// cut chart rect
// fn clip_on_chart(self: ChartView) void{
//   s := self.get_context().scale
//   sgl.scissor_rect(int(self._x_ * s), int(self._y_ * s), int(self._rect_chart.right()() * s), int(self._rect_chart.bottom()() * s), true)
// }

fn drawFrame(self: ChartView) void{
    // const w = self.base_.window_;
    const r = self._rect_rate;
    const d = self._rect_date;
    const c = Color.rgb(128, 128, 128); // global にして統一する
    Window.drawLine(r.left(), r.top(), r.left(), d.top(), c); // TODO remove gx.
    Window.drawLine(d.left(), d.top(), d.right(), d.top(), c);
}

fn drawTitle(self: ChartView) void{
    const sr = self._series_ orelse return;
    // const  v = self.base_;
    var buf: [10]u8 = undefined;
    const s = bufPrint(&buf, "{s} {}", .{sr.symbol_, sr.period_}) catch return;
    Window.drawText(0, 0, s, self._style.fntRate);
}

fn drawNone(self: ChartView) void{
    const r = self.base_.rect_;
    // std.log.debug("{d:.0}, {d:.0}\n", .{r.horizontalMid(), r.verticalMid()});
    Window.drawText(r.horizontalMid(), r.verticalMid(), "No Data", .{
        .h_align = .center, .v_align = .center,
        .size = 25, .color = Color.rgb(220, 220, 220)
    });
}

//----

/// 細線
inline fn ld(self: ChartView, x: f32) void{
    // const  v = self.base_;
    Window.drawLine(x, self._rect_chart.top(), x, self._rect_date.top(), self._style.color_grid_date_major);
}
/// 太線
inline fn ll(self: ChartView, x: f32) void{
    // const  v = self.base_;
    Window.drawLine(x, self._rect_chart.top(), x, self._rect_date.top(), self._style.color_grid_date_minor);
}
inline fn tx(self: ChartView, x: f32, comptime format: []const u8, args: anytype) void{
    // const  v = self.base_;
    var buf: [10]u8 = undefined;
    const s = bufPrint(&buf, format, args) catch return;
    Window.drawText(x, self._rect_date.top(), s, self._style.fntDate);
}
inline fn in(comptime T: type, val: T, src: []const T) bool{
    for (src) |s| if (s == val) return true;
    return false;
}

/// 日付軸
fn drawDateGrid(self: ChartView) void{
    const sr = self._series_ orelse return;
    var i: i32 = self._right_bar_index;
    while (i <= self._left_bar_index) : (i += 1) {
        if (i < 0 or i >= sr.size() - 1) continue; // FIXME integer overflow
        const oi = sr.get(@as(usize, @intCast(i))) catch continue;
        const oii = sr.get(@as(usize, @intCast(i + 1))) catch continue;
        const dt = oi.dt_;
        const dt1 = oii.dt_;
        const x = self.getX(i);
        switch (sr.period_) {
            .m1 => { // 15分毎に細線
                // if (dt.market_.minute_ in [15, 30, 45]) { ld(c, x); }
                if (in(u8, dt.market_.minute_, &[_]u8{15, 30, 45})) { self.ld(x); }
                if (dt.market_.hour_ != dt1.market_.hour_) {
                    self.ll(x);
                    self.tx(x, "{}", .{dt.local_.hour_});
                }
            },
            .m5 => { // 1時間ごとに太線、30分毎に細線
                if (dt.market_.minute_ == 0) {
                    self.ll(x);
                    if (dt.market_.hour_ != 0) { self.tx(x, "{}", .{dt.local_.hour_}); }
                } else if (dt.market_.minute_ == 30) { self.ld(x); }
                if (dt.market_.day_ != dt1.market_.day_) {
                    self.ll(x);
                    self.tx(x, "{}{s}", .{dt.local_.day_, dt.local_.week_.estr()});
                }
            },
            .m15 => { // 2時間おきに細線 4時間おきに太線
                if (dt.market_.minute_ == 0) {
                    if (dt.market_.hour_ % 4 == 0) {
                        self.ll(x);
                        if (dt.market_.hour_ == 0) {
                        } else { self.tx(x, "{}", .{dt.local_.hour_}); }
                    } else { self.ld(x); }
                }
                // 株のため
                if (dt.market_.day_ != dt1.market_.day_) {
                    self.ll(x);
                    self.tx(x, "{}{s}", .{dt.local_.day_, dt.local_.week_.estr()});
                }
            },
            .m30 => { // 4時間おきに細線 12時間おきに太線
                if (dt.market_.minute_ == 0) {
                    if (dt.market_.hour_ == 0) {
                        self.ll(x);
                        // self.tx(x, "$dt.local_.day_$dt.local_.week_.estr()")
                    } else if (dt.market_.hour_ % 4 == 0) {
                        self.ld(x);
                        if (dt.market_.hour_ % 8 == 0) { self.tx(x, "{}", .{dt.local_.hour_}); }
                    }
                }
                // 株のため
                if (dt.market_.day_ != dt1.market_.day_) {
                    self.ll(x);
                    self.tx(x, "{}{s}", .{dt.local_.day_, dt.local_.week_.estr()});
                }
            },
            .h1 => { // 1日おきに太線 8時間おきに細線 4時間ごとに線、8時間毎に文字
                if (dt.market_.minute_ == 0) {
                    if (dt.market_.hour_ == 0) {
                        self.ll(x);
                        // self.tx(x, "$dt.local_.day_$dt.local_.week_.estr()")
                    } else if (dt.market_.hour_ % 8 == 0) { self.ld(x); }
                }
                // 株のため
                if (dt.market_.day_ != dt1.market_.day_) {
                    self.ll(x);
                    self.tx(x, "{}{s}", .{dt.local_.day_, dt.local_.week_.estr()});
                    // TODO 現物株の場合はweekを省きたい
                }
            },
            .h4 => { // 月曜に太線 日毎に細線
                if (dt.market_.day_ != dt1.market_.day_) {
                    if (dt.market_.nweek_ != dt1.market_.nweek_) {
                        self.ll(x);
                        self.tx(x, "{}/{}", .{dt.local_.month_, dt.local_.day_});
                    } else { self.ld(x); }
                }
            },
            .d => { // 月ごとに太線、週ごとに細線
                if (dt.market_.month_ != dt1.market_.month_) {
                    self.ll(x);
                    // if (dt.market_.month_ in [1, 7]) {
                    if (in(u8, dt.market_.month_, &[_]u8{1, 7})) {
                        self.tx(x, "{}/{}/{}", .{dt.market_.year_, dt.market_.month_, dt.local_.day_});
                    } else { self.tx(x, "{}/{}", .{dt.market_.month_, dt.local_.day_}); }
                } else if (dt.market_.nweek_ != dt1.market_.nweek_) {
                    self.ld(x);
                    // TODO 15日前後だけを書きたい
                }
            },
            .w => { // 四半期ごとに太線、月ごとに細線
                if (dt.market_.month_ != dt1.market_.month_) {
                    self.ld(x);
                    // if (dt.market_.month_ in [1, 4, 7, 10]) {
                    if (in(u8, dt.market_.month_, &[_]u8{1, 4, 7, 10})) {
                        self.ll(x);
                        if (dt.market_.month_ == 1) { self.tx(x, "{}", .{dt.market_.year_}); }
                        else { self.tx(x, "{}", .{dt.market_.month_}); }
                    }
                }
            },
            .m => { // 1月に太線、7月に細線
                if (dt.market_.month_ == 1) {
                    self.ll(x);
                    self.tx(x, "{}", .{dt.market_.year_});
                } else if (dt.market_.month_ == 7) { self.ld(x); }
            },
            .q => { // 5年に太線、1年に細線
                if (dt.market_.month_ == 1) {
                    if (@mod(dt.market_.year_, 5) == 0) {
                        self.ll(x);
                        self.tx(x, "{}", .{dt.market_.year_});
                    } else { self.ld(x); }
                }
            },
            .y => { // 10年ごとに太線、5年毎に細線
                if (@mod(dt.market_.year_, 5) == 0) {
                    if (@mod(dt.market_.year_, 10) == 0) {
                        self.ll(x);
                        self.tx(x, "{}", .{dt.market_.year_});
                    } else { self.ld(x); }
                }
            }
        }
    }
}

const cfg_date_sub = gui.TextStyle{
    .size = 13,
    .color = Color.rgb(255, 255, 255),
    .h_align = .center,
    .vl_align = .bottom,
};

//----

/// 基準線
fn drawSubOrigin(self: ChartView) void{
    // const  v = self.base_;
    if (self._origin_sub_x <= 0) return; // セットされていない場合
    if (self._origin_sub_out) return;    // seriesの範囲外にある場合
    const x = self._origin_sub_x;
    Window.drawLine(x , 0, x, self._rect_chart.bottom(), Color.rgba(0, 0, 255, 100)); // TODO remove gx.
    // 中央揃え
    const s = self._origin_sub_time.?.str_iso8601_local_short();
    Window.drawText(x, self._rect_chart.bottom(), s, cfg_date_sub);
}

//----

fn match(symb: []const u8, group: []const []const u8) bool {
    for (group) |item| if (eql(u8, symb, item)) return true;
    return false;
}

/// symbolセット時に一度だけ実行させること TODO 未完
fn setRatePitch(self: *ChartView) void {
    var major: f64 = 0.0;
    var minor: f64 = 0.0;
    if (self._series_) |sr| {
        if (match(sr.symbol_, &[_][]const u8{"GBPUSD", "EURUSD", "AUDUSD", "USDCHF", "NZDUSD", "USDCAD"})) {
            switch (sr.period_) {
                .m1 => { major = 0.0005; minor = 0.001; },
                .m5, .m15 => { major = 0.001; minor = 0.005; },
                .m30, .h1 => { major = 0.002; minor = 0.01; },
                .h4 => { major = 0.005; minor = 0.01; },
                .d => { major = 0.01; minor = 0.05; },
                .w  => { major = 0.02; minor = 0.1; },
                .m => { major = 0.05; minor = 0.1; },
                else => { major = 0.05; minor = 0.1; }
            }
            self._rate_digits = 4;
        } else if (match(sr.symbol_, &[_][]const u8{"USDJPY", "EURJPY", "GBPJPY", "AUDJPY", "NZDJPY"})) {
            switch (sr.period_) {
                .m1 => { major = 0.05; minor = 0.1; },
                .m5, .m15 => { major = 0.1; minor = 0.5; },
                .m30, .h1 => { major = 0.2; minor = 1.0; },
                .h4 => { major = 0.5; minor = 1.0; },
                .d => { major = 1.0; minor = 5.0; },
                .w => { major = 2.0; minor = 10.0; },
                .m => { major = 5.0; minor = 10.0; },
                else => { major = 5.0; minor = 10.0; },
            }
            self._rate_digits = 2;
        } else  if (match(sr.symbol_, &[_][]const u8{"CL", "XAUUSD"})) {
            switch (sr.period_) {
                .m1 => { major = 5; minor = 10; },
                .m5, .m15 => { major = 10; minor = 50; },
                .m30, .h1 => { major = 20; minor = 100; },
                .h4 => { major = 50; minor = 100; },
                .d => { major = 100; minor = 500; },
                .w => { major = 200; minor = 1000; },
                .m => { major = 500; minor = 1000; },
                else => { major = 5000; minor = 10000; },
            }
            self._rate_digits = 0;
        } else if (match(sr.symbol_, &[_][]const u8{"DOW", "NSDQ"})) {
            switch (sr.period_) {
                .m1 => { major = 50; minor = 100; },
                .m5, .m15 => { major = 100; minor = 500; },
                .m30, .h1 => { major = 200; minor = 1000; },
                .h4 => { major = 500; minor = 1000; },
                .d => { major = 1000; minor = 5000; },
                .w => { major = 2000; minor = 10000; },
                .m => { major = 5000; minor = 10000; },
                else => { major = 50000; minor = 100000; },
            }
            self._rate_digits = 0;
        }
    }
    self._pitchMajor = major;
    self._pitchMinor = minor;
}

/// 0.0021 -> 3桁
/// 1.0021 -> 1桁
/// 1300.0 -> -3桁 TODO バグあり
fn rateDigit(d: f64) i8{
    if (d < 1) {
        var n = d;
        var count: i8 = 0;
        while (n < 1):(count +=1){
            n *= 10;
            // std.log.debug("{d:.5}\n",.{n});
        }
        return count;
    } else {
        var n = d;
        var count: i8 = 0;
        // 10 で剰余してあまりあれば行き過ぎた
        while (@mod(n, 10) == 0):(count +=1){
            n /= 10;
        }
        // FIX 1.3とか12001とかが処理できないバグ
        return -count - 1;
    }

    // fn rate_digit(d f64) int{
    //   /*
    //   0.0021 -> 3桁
    //   1.0021 -> 1桁
    //   1300.0 -> -3桁
    //   */
    //   s := '$d'
    //   if s[0] == `0` {
    //     for i in 2 .. s.len { // .以降
    //       if s[i] == `0` {
    //         continue
    //       }
    //       return i - 1
    //     }
    //   } else {
    //     mut dot := s.index('.') or { -1 }
    //     if dot == -1 { // 整数
    //       for {
    //         if s[s.len + dot] != `0` {
    //           return dot
    //         }
    //         dot--
    //       }
    //       return dot
    //     } else {
    //       // TODO ここ未完成
    //       for i := dot - 1; i > 0; i-- {
    //         if s[i] == `0` {
    //           i--
    //           continue
    //         }
    //         return -i
    //       }
    //     }
    //   }
    //   return 0
    // }
}

/// value を digit 桁で丸める
fn round2(value: f64, digit: i32) f64 {
    const p = std.math.pow(f64, 10.0, @as(f64, @floatFromInt(digit)));
    return std.math.round(value * p) / p;
}

// TODO 統合する
fn drawRateGrid(self: ChartView) void{
    _ = self._series_ orelse return;
    const highest = self._area_price_highest;
    const lowest = self._area_price_lowest;
    const keta = rateDigit(self._pitchMajor);
    const pw = std.math.pow(f64, 10.0, @as(f64, @floatFromInt(keta)));
    var level = round2(lowest, keta - 1);
    level -= self._pitchMajor * 5;
    // TODO 文字高より狭い場合は文字を間引く
    const pitch_height =  self._pitchMajor * self._v_scale;
    const ommit = pitch_height < 13; //self._style.fntRate.size;
    // println("${pitch}, ${self._v_scale}, ${self._style.fntRate.size}, $pitch_height, $ommit")
    while (level <= highest) {
        level += self._pitchMajor;
        if (level < lowest) continue;
        const y = self.getY(level);
        // TODO ここで範囲外に出ちゃうことを気にしなくて良いようにしたい
        if (y > self._rect_chart.top() and y < self._rect_chart.bottom()) {
            const lv = std.math.round(level * pw);
            const ph = std.math.round(self._pitchMinor * pw);
            const mj = @mod(lv, ph);

            const color = if (mj == 0.0) // 細線
                self._style.color_grid_rate_minor
            else self._style.color_grid_rate_major;
            Window.drawLine(self._rect_chart.left(), y, self._rect_chart.right(), y, color);

            // 間隔が狭すぎるときは間引く
            if (mj != 0.0 and ommit) continue;
            var buf: [10:0]u8 = undefined;
            const s = bufPrint(&buf, "{[0]d:.[1]}", .{level, self._rate_digits}) catch continue; // TODO etc. "{d: .2}"
            // TODO 細線は色を変えるかサイズを小さくする
            Window.drawText(self._rect_chart.right(), y, s, self._style.fntRate);
        }
    }
    // 最高値安値
    const rr = self._rect_rate;
    const tr = self._style.fntRate;
    var buf: [10:0]u8 = undefined;
    var s = bufPrint(&buf, "{[0]d:.[1]}", .{self._area_price_highest, self._rate_digits}) catch return;
    Window.drawText(rr.left(), rr.top() + tr.size / 2, s, tr);
    s = bufPrint(&buf, "{[0]d:.[1]}", .{self._area_price_lowest, self._rate_digits}) catch return;
    Window.drawText(rr.left(),  rr.bottom() - tr.size / 2, s, tr);
}

/// Deprecated?
fn ratePitch(h: f64, l: f64, num: i32) f64{
    const dif = h - l;
    const v = dif / @as(f64, @floatFromInt(num));
    const d = rateDigit(v); // TODO
    const p = round2(v, d);
    // 5か2か1にする
    // numも絡めて調整、したい..
    const b = 5 * std.math.pow(f64, 10.0, @as(f64, @floatFromInt(-d))); // TODO
    var pp = b * std.math.round(p / b); // TODO
    if (pp == 0.0) pp = p;
    if (pp < 0.0005) pp = 0.0005; // TODO gbpusdのみ
    return pp;
}

/// Deprecated 値軸
fn _drawRateGrid(self: ChartView) void{
    const sr = self._series_ orelse return;
    // if (self._reverse)
    //   Window.drawText(self._rect_chart.horizontalMid(), self._rect_chart.verticalMid(), "Reverse", cfg_rev);
    if (eql(u8, sr.symbol_, "GBPUSD")) {
        self.drawRateGridFixed();
        return;
    }
    const n = 7;
    const highest = self._area_price_highest;
    const lowest = self._area_price_lowest;
    const pitch = ratePitch(highest, lowest, n); // BUG LEAK
    if (pitch == 0.0) return;
    const keta = rateDigit(pitch);
    var level = round2(lowest, keta - 1);
    level -= pitch * n;
    // c1 := $if linux {color_date_grid_1l} $else {color_date_grid_1a}
    while (level <= highest) {
        // level = util.round(f64(level + pitch), keta)
        level += pitch;
        if (level < lowest) continue;
        const y = self.getY(level);
        // TODO ここで範囲外に出ちゃうことを気にしなくて良いようにしたい
        if (y > 0 and y < self._rect_chart.bottom()) {
            // Window.drawLine(self.0, y, self._rect_chart.right(), y, self._style.color_grid_date_major);
            Window.drawLine(self._rect_chart.left(), y, self._rect_chart.right(), y, self._style.color_grid_date_major);

            // const s = "${level:.4f}";
            // sy := y // - self._style.fntRate.size / 2
            // Window.drawText(self._rect_chart.right(), int(y), s, self._style.fntRate);
            
            // 間隔が狭すぎるときは間引く
            // if (mj != 0.0 and ommit) continue;
            var buf: [10:0]u8 = undefined;
            const s = bufPrint(&buf, "{d:.4}", .{level}) catch continue;
            // TODO 細線は色を変えるかサイズを小さくする
            Window.drawText(self._rect_chart.right(), y, s, self._style.fntRate);
        }
    }
    
    // const rh = ((self._area_price_highest / self._area_price_lowest) - 1) * 100;
    // // const rl = (1 - (self._area_price_lowest / self._area_price_highest)) * 100;
    // var s = "${rh:.1}-${rl:.1}%";
    // if (rh >= 10.0) s = "${rh:.0}-${rl:.0}%";
    // // const y = self._rect_chart.bottom() + self._style.fntRate.size / 2;
    // // Window.drawText(self._rect_chart.right(), y, s, self._style.fntRate);
}

/// Deprecated GBPUSDだけ特別
fn drawRateGridFixed(self: ChartView) void{
    const sr = self._series_ orelse return;
    // const  v = self.base_;
    const highest = self._area_price_highest;
    const lowest = self._area_price_lowest;
    var pitchS: f64 = 0;
    var pitchL: f64 = 0;
    switch (sr.period_) {
        .m1 => { pitchS = 0.0005; pitchL = 0.001; },
        .m5, .m15 => { pitchS = 0.001; pitchL = 0.005; },
        .m30, .h1 => { pitchS = 0.002; pitchL = 0.01; },
        .h4 => { pitchS = 0.005; pitchL = 0.01; },
        .d => { pitchS = 0.01; pitchL = 0.05; },
        .w  => { pitchS = 0.02; pitchL = 0.1; },
        .m => { pitchS = 0.05; pitchL = 0.1; },
        else => { pitchS = 0.05; pitchL = 0.1; }
    }
    // std.log.debug("{}, {}\n", .{pitchS, pitchL});
    const keta = rateDigit(pitchS);
    const pw = std.math.pow(f64, 10.0, @as(f64, @floatFromInt(keta)));
    var level = round2(lowest, keta - 1);
    level -= pitchS * 5;
    // TODO 文字高より狭い場合は文字を間引く
    const pitch_height =  pitchS * self._v_scale;
    const ommit = pitch_height < 13; //self._style.fntRate.size;
    // println("${pitch}, ${self._v_scale}, ${self._style.fntRate.size}, $pitch_height, $ommit")
    while (level <= highest) {
        level += pitchS;
        if (level < lowest) continue;
        const y = self.getY(level);
        // TODO ここで範囲外に出ちゃうことを気にしなくて良いようにしたい
        if (y > self._rect_chart.top() and y < self._rect_chart.bottom()) {
            const lv = std.math.round(level * pw);
            const ph = std.math.round(pitchL * pw);
            const mj = @mod(lv, ph);

            const color = if (mj == 0.0) // 細線
                self._style.color_grid_rate_minor
            else self._style.color_grid_rate_major;
            Window.drawLine(self._rect_chart.left(), y, self._rect_chart.right(), y, color);

            // 間隔が狭すぎるときは間引く
            if (mj != 0.0 and ommit) continue;
            var buf: [10:0]u8 = undefined;
            const s = bufPrint(&buf, "{d:.4}", .{level}) catch continue;
            // TODO 細線は色を変えるかサイズを小さくする
            Window.drawText(self._rect_chart.right(), y, s, self._style.fntRate);
        }
    }
    // 最高値安値
    const rr = self._rect_rate;
    const tr = self._style.fntRate;
    var buf: [10:0]u8 = undefined;
    var s = bufPrint(&buf, "{d:.4}", .{self._area_price_highest}) catch return;
    Window.drawText(rr.left(), rr.top() + tr.size / 2, s, tr);
    s = bufPrint(&buf, "{d:.4}", .{self._area_price_lowest}) catch return;
    Window.drawText(rr.left(),  rr.bottom() - tr.size / 2, s, tr);
}

//----

fn drawBidAsk(self: ChartView) void{
    const sr = self._series_ orelse return;
    // const v = self.base_;
    const ba = sr.bidask orelse return;
    const ya = self.getY(ba.ask_);
    const yb = self.getY(ba.bid_);
    // const rc = self._rect_chart;
    if (ya >= 0 and ya <= self._rect_chart.bottom()) {
        Window.drawLine(0, ya, self._rect_chart.right(), ya, Color.rgb(128, 128, 128)); // TODO remove gx.
        // var buf: []u8 = undefined;
        // const s = bufPrint(buf, "{d:.5}", .{ba.ask});
        // Window.drawText(0, ya - self._style.fntRate.size, s, self._style.fntRate);
    }
    if (yb >= 0 and yb <= self._rect_chart.bottom()) {
        Window.drawLine(0, yb, self._rect_chart.right(), yb, Color.rgb(128, 128, 128));
        // var buf: []u8 = undefined;
        // const s = bufPrint(buf, "{d:.5}", .{ba.bid});
        // Window.drawText(0, yb + 1, s, self._style.fntRate);
    }
}

fn drawOhlc(self: ChartView) void{
    const sr = self._series_ orelse return;
    // const  v = self.base_;
    const w: f32 = 2; // Barの幅の半分、か?
    var i = self._right_bar_index;
    while (i <= self._left_bar_index) : (i += 1) {
        if (i < 0 or i >= sr.size()) continue;
        const o = sr.get(@as(usize, @intCast(i))) catch continue;
        if (o.isZero()) continue;
        const x = self.getX(i);
        const yh = self.getY(o.high);
        const yl = self.getY(o.low);
        const yo = self.getY(o.open);
        const yc = self.getY(o.close);
        // std.log.debug("yc={d:.0}\n", .{yc});
        Window.drawLine(x, yh, x, yl, self._style.bar_color);
        if (o.close > o.open)
            Window.drawRect(x - 1, yo, w, yc - yo, self._style.bar_color_body_up, true)
        else if (o.close < o.open)
            Window.drawRect(x - 1, yo, w, yc - yo, self._style.bar_color_body_down, true)
        else
            Window.drawLine(x - 1, yc, x + w, yc, self._style.bar_color);
        Window.drawRect(x - 1, yo, w, yc - yo, self._style.bar_color, false);
        //終値
        // $if !android {
        //   v.draw_circle_filled(x, yc, 1.2, gx.white) // TODO remove gx.
        // }
    }
}

//-------------------------------------------

pub fn toggleShowObjects(self: *ChartView) void{
    self.is_show_objects = !self.is_show_objects;
    // if (self._is_show_objects){
    //   self.obj_horizontal_lines.read();
    // } else {
        
    // }
}

pub inline fn getSeries(self: ChartView) ?*bar.OHLCSeries {
    return self._series_;
}

pub inline fn symbol(self: ChartView) ?[]const u8 {
    return self._series_.?.symbol_;
}

pub inline fn period(self: ChartView) ?bar.Period {
    return if (self._series_) |s| s.period_ else null;
}

pub inline fn setStyle(self: *ChartView, style: Style) void{
    self._style = style;
}

pub inline fn getVScale(self: ChartView) f32 {
    return self._v_scale;
}

pub inline fn getAreaHighest(self: ChartView) f64 {
    return self._area_price_highest;
}

pub inline fn getAreaLowest(self: ChartView) f64 {
    return self._area_price_lowest;
}

/// `value`=0で解除
pub fn setFixedVScale(self: *ChartView, value: f64) void{
    self._fixed_v_scale = f32(value);
    self.calcVScale();
}

/// ObjOverlaysのMaxVScaleを加味する
pub fn useMaxVScale(self: *ChartView, on: bool) void {
    self._use_max_v_scale = on;
    self.calcVScale();
}

pub fn getOriginSubTime(self: ChartView) ?bar.MTime {
    return self._origin_sub_time;
}

//----

pub fn numAt(self: ChartView, x: f32) i32{
    const val = -(x - self.originX() + self._bar_width_ / 2) / self._bar_width_;
    return @intFromFloat(val);
}

pub fn barAt(self: ChartView, x: f32) ?*bar.OHLC{
    const sr = self._series_.?;
    const i = self.numAt(x);
    return if (i < 0 or i >= sr.size()) null
    else sr.get(@as(usize, @intCast(i))) catch null;
}

/// y はEvenの値だからWindwoに対する絶対位置
pub fn priceAt(self: ChartView, y: f64) ?f64 {
    const _y = ((y - self._rect_chart.verticalMid()) / self._v_scale);
    return if (!self._reverse) self._area_price_center - _y
    else self._area_price_center + _y;
}

pub fn xAt(self: ChartView, date: bar.MTime) !f32{
    const n = try self.barN(date);
    return self.getX(n);
}

/// TODO
pub fn yAt(self: ChartView, price: f64) ?f32{
    _ = self;
    _ = price;
    return 0.0;
}

// // overlayも含めて
// fn getBar(self: ChartView, symbol: []const u8, n: i32) !bar.OHLC{
//   if (eql(u8, symbol, self.symbol())) return self._series_.get(n);
//   for (self.obj_overlays) |o| {
//     if (eql(u8, symbol, self.symbol())) {
//       return o.overlay.get(n);
//     }
//   }
//   return error.NotFound; //("no bar found.")
// }

// // overlayも含めて
// fn get_y(self: ChartView, symbol: []const u8, price: f64) f32{
//   if (eql(u8, symbol, self.symbol())) return self.getY(price);
//   for (self.obj_overlays) |o| {
//     if (eql(u8, symbol, self.symbol())) {
//       return o.y(price, f32(self._rect_chart.verticalMid()));
//     }
//   }
//   return 0.0;
// }

// // overlayも含めて
// fn getPriceAt(self: ChartView, symbol: []const u8, y: f64) ?f64{
//   if (eql(u8, symbol, self.symbol())) return self.priceAt(y);
//   for (self.obj_overlays) |o| {
//     if (eql(u8, o.symbol(), symbol)) {
//       // TODO reverse
//       return o.price_area_center - ((y - self._rect_chart.verticalMid()) / o.v_scale);
//     }
//   }
//   return 0.0;
// }

//----

pub fn insideChartRect(self: ChartView, x: f32, y: f32) bool {
    return self._rect_chart.contains(x, y);
}

pub fn insidePriceRect(self: ChartView, x: f32, y: f32) bool {
    const r = self.base_.rect_;
    return (r._x_ + self._rect_chart.right() <= x and x <= r.right())
        and (r._y_ <= y and y <= r._y_ + self._rect_chart.bottom());
}

pub fn insideDateRect(self: ChartView, x: f32, y: f32) bool {
    const r = self.base_.rect_;
    return (r._x_ <= x and x <= r._x_ + self._rect_chart.right())
        and (r._y_ + self._rect_chart.bottom() <= y and y <= r.bottom());
}

//-------------------------------------------

pub fn onEvent(impl: *anyopaque, e: gui.Event) bool {
    var self: *ChartView = @alignCast(@ptrCast(impl));
    // _=self;
    switch (e.type){
        .mouse_down, .touches_began => self.onMouseDown(e),
        .mouse_move, .touches_moved => self.onMouseMove(e),
        .mouse_up, .touches_ended => self.onMouseUp(e),
        .mouse_scroll => self.onScroll(e),
        else => {},
    }
    return false;
}

fn onMouseDown(self: *ChartView, e: gui.Event) void {
    if (self.insideChartRect(e.x(), e.y())) {
        // std.log.debug("mouse_mode={}\n", .{self.mouse_mode});
        switch (self.mouse_mode) {
            .default => {
                Mouse.down(e);
                Mouse.setXY(e);
                if (e.modifiers == key_mod_shift and e.mouse_button == .left) {
                    // TODO 拡大?を割り当てたい
                } else if (e.modifiers == key_mod_ctrl and e.mouse_button == .left) {
                } else {
                    // popup-menu
                    if (e.mouse_button == .right) {
                        std.log.debug("Right Click", .{});
                        // if (self.context_menu) |cm| {
                        //   _=cm;
                            // var cm = menu.new(view: cw, x: int(e.x()), y: int(e.y()),
                            //   items: self.context_menu);
                            // cm.show();
                        // }
                    }
                }
            },
            .cross_hair => {},
            .channel_line => {
                Mouse.down(e);
                switch (self.obj_trend_channels.editor.mode) {
                    .add => {
                        self.obj_trend_channels.editor.place(e, self);
                    },
                    .remove => {
                        // $if linux {
                        //   // already hit_test and changed to selected color by mouse_move
                            self.obj_trend_channels.editor.remove(e, self, 3);
                        // }
                        // $if android {
                        //   // TODO long tap -> msg_box -> confirm -> delete
                        //   self.channel_editor.remove(e, self, 10)
                        // }
                        // // n := $if android { 10 } $else { 3 }
                        // // self.channel_editor.remove(e, mut self.Chart, n)
                    },
                    .channel => {
                        // select -> temp?
                        // $if linux {
                            self.obj_trend_channels.editor.placeChannel(e, self, 3);
                        // }
                        // $if android {
                        //   self.channel_editor.place_channel(e, self, 10)  
                        // }
                    },
                }
            },
            .horizontal_line => {
                Mouse.down(e);
                switch (self.obj_horizontal_lines.editor.mode) {
                    .add => self.obj_horizontal_lines.editor.place(e, self),
                    .remove => {
                        // $if linux {
                            self.obj_horizontal_lines.editor.remove(e, self, 3);
                        // }
                        // $if android {
                        //   self.hline_editor.remove(e, self, 10)
                        // }
                    },
                }
            },
            .vertical_line => {},
            .zigzag => {
                // // barを取得
                // var c = cw;
                // const b = self.barAt(e.x()) or { return }
                // const p = self.priceAt(e.y()) or { return }
                // const n = util.near(p, b.high, b.low);
                // self.obj_temp_zigzag.add(b.period,b.dt,n);
                // // println(self.obj_temp_zigzag.last())
            },
            .memo => {
                // Mouse.down(e.x(), e.y());
                // // hit_test to get an obj_memo
                // // ドラッグドロップで移動、サイズ変更
                // self.memo_editor.down(e.x(), e.y(), &cw);
            },
        }
    } else if (self.insidePriceRect(e.x(), e.y())) {
        // TODO 縦縮尺変更
        // 上に引っ張れば下が広がる(最安値を下に設定する)
        switch (self.mouse_mode) {
            .default => {
                if (e.isDown()) {
                }
            },
            else => {}
        }
    }
}

fn onMouseMove(self: *ChartView, e: gui.Event) void {
    switch (self.mouse_mode) {
        .default => {
            // ドラッグ
            if (Mouse.isDown()) {
            // チャートを横ドラッグでスクロール
                // if (self.insideChartRect(Mouse._down_x, Mouse._down_y)) {
                if (self.insideChartRect(e.x(), e.y())) {
                    const delta_x = e.x() - Mouse.getPrevXY().x;
                    // std.log.debug("onMouseMove! delta_x={}, {}\n", .{delta_x, self._rect_chart});
                    self.offsetOrigin(delta_x);
                    Mouse.setXY(e);
                }
                // // TODO プライス軸を縦ドラッグで縮率調整
                // else if (self.insidePriceRect(Mouse._down_x, Mouse._down_y)) {
                //   const drag_y = e.y() - Mouse._prev_y;
                //   // dump(drag_y)
                //   // 上にドラッグはマイナス値=より狭範囲に可視化=縮率小さく
                //   if (drag_y < 0) {
                //   }
                //   // 下にドラッグはプラス=より広範囲に可視化=縮率大きく
                //   else if (drag_y > 0) {
                //   }
                // }
            }
        },
        .cross_hair => {
            if (self.insideChartRect(e.x(), e.y())) {
                if (self.barAt(e.x())) |b| {
                    const p = self.priceAt(e.y()) orelse return;
                    const n = self.numAt(e.x());
                    self._temp_crosshair = CrossHair{
                        .bar = b.*,
                        .price = p,
                        .num = n
                    };
                }
            }
            // TODO ドラッグとスクロール
        },
        .channel_line => {
            switch (self.obj_trend_channels.editor.mode) {
                .add => {
                    if (Mouse.isDown()) {
                        self.obj_trend_channels.editor.place(e, self);
                    }
                },
                .remove => {
                    // $if linux {
                    //   // TODO hit_test _> change to selected color
                    //   // self.chart.hit_test_trend_channel(int(e.x), int(e.y))
                    // }
                },
                .channel => {
                    // move parallel
                },
            }
        },
        .horizontal_line => {
            if (Mouse.isDown()) self.obj_horizontal_lines.editor.place(e, self);
        },
        .vertical_line => {},
        .zigzag => {},
        .memo => {
            // // 移動、サイズ変更
            // if (Mouse.isDown()) {
            //   self.memo_editor.move(e.x(), e.y(), cw);
            // }
        },
    }
}

/// pointInside() をしないことに注意
fn onMouseUp(self: *ChartView, e: gui.Event) void {
    _ = e;
    switch (self.mouse_mode) {
        .default => Mouse.up(),
        .cross_hair => {},
        .channel_line => {
            // app := 
            // mut cl := self.channel_editor.get_object(self.chart)
            // cl.to_detailed_time()
            // println(cl)
            Mouse.up();
            // write
            // ch.write_trend_channel()
        },
        .horizontal_line => Mouse.up(),
        .vertical_line => {},
        .zigzag => {},
        .memo => Mouse.up(),
    }
}

fn onScroll(self: *ChartView, e: gui.Event) void {
    if (self.insideChartRect(e.x(), e.y())) {
        const scroll_n = -e.scroll_y * 4 * 6;
        if (self.insideChartRect(e.x(), e.y())) self.offsetOrigin(scroll_n);
    }
}

const Point = struct {
    x: f32,
    y: f32,
};

/// TODO ゼスチャー等
const Mouse = struct {
    // ドラッグ
    // 長押し
    // ダブルクリック
    // スワイプ
    // フリック
    const _double_click_time_: i64 = 300000000; // 0.3秒
    var _down_x: f32 = 0; // down位置
    var _down_y: f32 = 0; //
    var _is_down: bool = false; // drag用
    var _down_time: i64 = 0;  // double click 検出用
    // var _up_time: i64 = 0;
    var _prev_x: f32 = 0;  // drag scrooll
    var _prev_y: f32= 0;  //
    // var _pressed_time: i64 = 0; // 押していた時間
    // TODO
    var onDoubleClicked: ?*const fn () void = null;
    var onLongPressed: ?*const fn () void = null;

    /// 押したx,yを記録
    fn down(e: gui.Event) void {
        _down_x = e.x();
        _down_y = e.y();
        _is_down = true;
        _down_time = std.time.milliTimestamp();
    }

    /// 押された状態である
    fn isDown() bool {
        return _is_down;
    }

    /// dragすると _prev_x は Event.x() に置き換わる
    fn setXY(e: gui.Event) void {
        _prev_x = e.x();
        _prev_y = e.y();
    }

    fn up() void {
        _is_down = false;
        // if () {
        //   if (onDoubleClicked) |on| on();
        // }
    }
    
    fn getXY() Point {
        return .{.x = _down_x, .y = _down_y};
    }
    
    fn getPrevXY() Point {
        return .{.x = _prev_x, .y = _prev_y};
    }
};

const key_mod_alt = 2;
const key_mod_super = 256;
const key_mod_shift = 257;
const key_mod_ctrl = 258;
// ctrl + shift = 259
// shift + alt = 261
// ctrl + alt = 262
// ctrl + alt + shift = 263

pub const MouseMode = enum{
    default,
    cross_hair,
    channel_line,
    horizontal_line,
    vertical_line,
    zigzag,
    memo,
};

pub fn getMouseMode(self: ChartView) MouseMode {
    return self.mouse_mode;
}

pub fn setMouseMode(self: *ChartView, mode: MouseMode) void {
    self.mouse_mode = mode;
    // これでtempを消せる
    self._temp_crosshair = .{};
    self.obj_trend_channels.temp = null;
    self.obj_horizontal_lines.temp = null;
}

Expected Behavior

Correct folding

Relevant log output

No response

@tsokano tsokano added the bug Something isn't working label Dec 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant