重ねた紙片の奥にある紙のドラッグとクリック

一番手前にある紙片をドラッグで移動、 クリックで編集開始するのは自然なことです。 では奥にある紙片では、 どうするのが良いのでしょうか。

https://tociyuki.sakura.ne.jp/archive/postit.html

https://tociyuki.sakura.ne.jp/archive/postit.js

奥にある紙片をドラッグもしくはクリックすると一番手前へ並べかえてから、 移動・編集開始をするのが一つの方法です。 これは紙片をウィンドウのようなものと見立てて、 デスクトップのウィンドウ・マネジャと同じ流儀で扱うことに相当します。

一方、 ドラッグしてもクリックしても、 紙片の重なり順を変更せず、 奥のものは奥にあるままで、 移動・編集する流儀もあります。 これは、 紙片をドロー・アプリケーションの図形のようなものと見立てることに相当します。

両方を試してみた結果、 後者の重なり順を変更せずにドラッグする方が、 紙片の山を扱うのに向いているような気がしてきました。 紙片を重ねた山の途中から紙片をドラッグで抜き取り、 内容を読んで、 またもやドラッグで山に突っ込むと、 順番を変えずに途中を読むことができます。 順番を変えたいときは、 奥の紙片をクリックして一番手前へ浮き上がらせれば良いでしょう。 編集で順番を変えたくないので、 順番変更のためのクリックと編集開始のクリックを、 もっともらしい理屈で区別できれば解決です。 そこで思いついたのは、 順番変更をしたいときは山の下にある紙片を指定することから、 見た目で上に紙片が重なっているものをクリックしたら順番変更とみなすことでした。 編集開始は上に紙片が被っていないものをクリックしたときとします。編集の場合は、 重なりの順番を変更しないことにします。

このやりかたで、 紙片の山の途中を順番を変えずに編集するには、 まず、 山の途中から紙片を引っ張り出して、 全面を露出させます。 そしてクリックして編集をおこないます。 編集が終わったら、 今度は山へドラッグして突っ込みます。 マウス操作は直感に合っています。

見た目で上に紙片が重なっているかどうかは、 zorder 配列に昇順で四角形の重なりがあるかどうかを調べることで判定できます。 四角形の重なりは、 4 隅の頂点の座標か、 幅と高さがあれば判定できます。 紙片を移動しても同じ値のままで良い手軽さから、 幅と高さを付箋紙オブジェクトに記録することにします。

  Cardboard.prototype.width = function(id) { return this.cards[id].width; };
  Cardboard.prototype.height = function(id) { return this.cards[id].height; };

  Cardboard.prototype.set_width_height = function(id, x, y) {
    this.cards[id].width = x;
    this.cards[id].height = y;
  };

四角形の重なりがあるかどうかの判定には、 定石を使います。

  Cardboard.prototype.check_overwrap_cards = function(id1, id2) {
    const card1 = this.cards[id1];
    const card2 = this.cards[id2];
    const lft = Math.max(card1.left, card2.left);
    const top = Math.max(card1.top, card2.top);
    const rgt = Math.min(card1.left + card1.width - 1, card2.left + card2.width - 1);
    const bot = Math.min(card1.top + card1.height - 1, card2.top + card2.height - 1);
    return lft <= rgt && top <= bot;
  };

これで、 id の紙片が全面露出しているか、 すなわち、 それの上に他の紙片が被っていないことを調べられるようになります。

  Cardboard.prototype.isexposing = function(id) {
    const ifirst = this.zorder.indexOf(id) + 1;
    const ilast = this.zorder.length;
    let exposing = true;
    for (let i = ifirst; exposing && i < ilast; ++i)
      exposing = ! this.check_overwrap_cards(id, this.zorder[i]);
    return exposing;
  };

紙片の幅と高さは HTML の埋め込み CSS で指定しているので、 div 要素の作成時にそれを読み取ることにします。

  const handle_createcard = function(ev, board, id) {
    $('body').append(
      $('<div></div>').attr('id', strip_sharp(id)).attr('class', 'bx'));
    $(id).css('position', 'absolute')
      .css('z-index', (100 + board.zidx(id)).toString())
      .on('mousedown', {'board': board}, handle_div_mousedown);
    board.set_width_height(id, $(id).width(), $(id).height());
    handle_movecard(ev, board, id);
    handle_changecard(ev, board, id);
  };

mouseup イベント・ハンドラのクリック時に、 モデルの isexposing が偽のときに一番手前に重なり順を変更します。 真のときに編集を開始します。

  const handle_div_mouseup = function(ev) {
    $(this).off('mousemove');
    const ctl = ev.data;
    ctl.board.set_position(ctl.id, ctl.left, ctl.top);
    if (! ctl.isclick(ev)) {
      /* do nothing */;
    }
    else if (! ctl.board.isexposing(ctl.id)) {
      ctl.board.bring_to_front(ctl.id);
    }
    else {
      $(this).hide();
      $(TE).width($(this).width()).height($(this).height())
        .css('left', ctl.left.toString () + 'px')
        .css('top', ctl.top.toString () + 'px')
        .val(ctl.board.value(ctl.id))
        .one('blur', ctl, handle_te_blur)
        .show().focus();
    }
  };