Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.12. Реакция блоков на действия пользователя

§2.12.8. Реакция на действия пользователя при редактировании схемы

Полный исходный текст на языке C++ для библиотеки (DLL) с моделями блоков, реагирующих на действия пользователя в режиме редактирования. Библиотека содержит три модели:

- OpenSysWin – модель блока, добавляющего в главное меню RDS пункт для открытия родительской подсистемы (см. §2.12.7, теперь окно настроек автоматически открывается при добавлении блока из библиотеки);
- TestBlkMoveSetter – модель блока, записывающего в динамические переменные значения скорости и угла поворота (см. §2.6.4, теперь при удалении этого блока пользователем выводится предупреждение о том, что другие блоки потеряют с ном связь);
- TestBlkMoveObject – модель блока, перемещающегося в окне подсистемы со скоростью и в направлении, считанными из динамических переменных (изменений в модели нет);
- SimpleJoystick – модель блока, изображающего двухкоординатную рукоятку, которую пользователь может перемещать мышью (см. §2.12.2, теперь блок все время остается квадратным).

Изменения относительно предыдущих версий этих моделей выделены цветом.

      // Реакция на действия пользователя в режиме редактирования
      #include <windows.h>
      #include <math.h>
      #include <RdsDef.h>
      // Подготовка описаний сервисных функций
      #define RDS_SERV_FUNC_BODY GetInterfaceFunctions
      #include <RdsFunc.h>

      //========== Главная функция DLL ==========
      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          { // Получение доступа к функциям RDS
            if(!GetInterfaceFunctions())
              RDS_SERV_ERROR_MSGW // Сообщение: старая версия RDS
          }
        return 1;
      }
      //========= Конец главной функции =========

      //=========================================================================
      // Реакция на ручную вставку блока
      //=========================================================================

      // Класс личной области данных блока
      class TOpenSysWinData
      { public:
          RDS_MENUITEM MenuItem; // Идентификатор созданного пункта меню
          // Настроечные параметры
          char *Caption;         // Название пункта
          int Key;               // Клавиша (или 0,если ее нет)
          DWORD KeyShifts;       // Состояние Ctrl, Alt и Shift

          // Создание пункта меню с заданными параметрами или
          // изменение параметров уже созданного пункта
          void RegisterMenuItem(void);

          int Setup(void);    // Открыть окно настройки
          void SaveBin(void); // Сохранить параметры
          int LoadBin(void);  // Загрузить параметры

          // Конструктор класса
          TOpenSysWinData(void)
            { Caption=NULL;
              Key=0; KeyShifts=0;
              MenuItem=NULL;
            };
          // Деструктор класса
          ~TOpenSysWinData()
            { // Освободить память, занятую строкой Caption
              rdsFree(Caption);
              // Удалить пункт меню
              rdsUnregisterMenuItem(MenuItem);
            };
      };
      //=========================================

      // Создание или модификация пункта меню
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TOpenSysWinData::RegisterMenuItem(void)
      { // Вспомогательная переменная для заголовка пункта меню:
        // если Caption==NULL, пункт получит заголовок "Открыть окно"
        const char *text=Caption?Caption:"Открыть окно";
        // Флаги пункта меню: если клавиша определена (Key!=0),
        // пункт будет иметь "горячую клавишу"
        DWORD options=Key?RDS_MENU_SHORTCUT:0;

        if(MenuItem) // Пункт уже есть - изменяем
          rdsChangeMenuItem(MenuItem,text,options,Key,KeyShifts,0,0);
        else // Пункта еще нет - создаем
          MenuItem=rdsRegisterMenuItem(text,options,Key,KeyShifts,0,0);
      }
      //=========================================

      // Запись параметров блока
      void TOpenSysWinData::SaveBin(void)
      { BYTE tag; // Переменная для байта тега
        int len=Caption?strlen(Caption):0; // Длина строки Caption

        tag=1; // Тег 1 – название пункта меню
        rdsWriteBlockData(&tag,sizeof(tag));
        rdsWriteBlockData(&len,sizeof(len)); // Длина строки
        if(len) // При ненулевой длине – запись самой строки
          rdsWriteBlockData(Caption,len);

        tag=2; // Тег 2 – "горячая клавиша"
        rdsWriteBlockData(&tag,sizeof(tag));
        rdsWriteBlockData(&Key,sizeof(Key));
        rdsWriteBlockData(&KeyShifts,sizeof(KeyShifts));

        tag=0; // Тег 0 – конец данных
        rdsWriteBlockData(&tag,sizeof(tag));
      }
      //=========================================

      // Загрузка параметров блока
      int TOpenSysWinData::LoadBin(void)
      { BYTE tag; // Переменная для байта тега
        int len;

        // Прежде всего, освобождаем память, которую занимало старое
        // название пункта меню – сейчас загрузится новое
        rdsFree(Caption);
        Caption=NULL;

        for(;;)
          { if(!rdsReadBlockData(&tag,sizeof(tag)))
              break; // Тег не считан – данные неожиданно кончились
            switch(tag)
              { case 0: // Конец данных
                  RegisterMenuItem(); // Создаем или изменяем пункт меню
                  return RDS_BFR_DONE;
                case 1: // Название пункта
                  rdsReadBlockData(&len,sizeof(len)); // Читаем длину
                  if(len) // Длина ненулевая – читаем len байтов строки
                    { // Отводим место с учетом нуля в конце строки
                      Caption=(char*)rdsAllocate(len+1);
                      rdsReadBlockData(Caption,len); // Читаем строку
                      Caption[len]=0; // Дописываем нулевой байт в конец
                    }
                  break;
                case 2: // Данные "горячей клавиши"
                  rdsReadBlockData(&Key,sizeof(Key));
                  rdsReadBlockData(&KeyShifts,sizeof(KeyShifts));
                  break;
                default: // Тег не опознан – ошибка
                  return RDS_BFR_ERROR;
              }
          }
        // Вышли из цикла из-за неожиданного конца данных блока - ошибка
        return RDS_BFR_ERROR;
      }
      //=========================================

      // Настройка параметров блока
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      int TOpenSysWinData::Setup(void)
      { RDS_HOBJECT window; // Идентификатор объекта-окна
        BOOL ok;            // Пользователь нажал "OK"
        // Создание окна
        window=rdsFORMCreate(FALSE,-1,-1,"Открытие окна");
        // Поле ввода для строки названия
        rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_EDIT,
            "Текст пункта меню:",200);
        rdsSetObjectStr(window,1,RDS_FORMVAL_VALUE,Caption);
        // Поле ввода для "горячей клавиши"
        rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_HOTKEY,
            "Клавиша:",150);
        rdsSetObjectInt(window,2,RDS_FORMVAL_VALUE,Key);
        rdsSetObjectInt(window,2,RDS_FORMVAL_HKSHIFTS,KeyShifts);
        // Открытие окна
        ok=rdsFORMShowModalEx(window,NULL);
        if(ok)
          { // Нажата кнопка OK – запись параметров в блок
            rdsFree(Caption); // Уничтожение старой строки
            // Создание динамической копии введенной строки
            Caption=rdsDynStrCopy(rdsGetObjectStr(window,1,
                RDS_FORMVAL_VALUE));
            // Чтение параметров клавиши
            Key=rdsGetObjectInt(window,2,RDS_FORMVAL_VALUE);
            KeyShifts=rdsGetObjectInt(window,2,RDS_FORMVAL_HKSHIFTS);
            // Создание пункта меню на основе изменившихся параметров
            RegisterMenuItem();
          }
        // Уничтожение окна
        rdsDeleteObject(window);
        // Возвращаемое значение
        return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE;
      }
      //=========================================

      // Блок, открывающий окно родительской подсистемы
      extern "C" __declspec(dllexport)
        int RDSCALL OpenSysWin(int CallMode,
                          RDS_PBLOCKDATA BlockData,
                          LPVOID ExtParam)
      { // Указатель на личную область данных блока,приведенный к
        // правильному типу
        TOpenSysWinData *data=(TOpenSysWinData*)(BlockData->BlockData);
        switch(CallMode)
          { case RDS_BFM_INIT: // Инициализация
              BlockData->BlockData=data=new TOpenSysWinData();
              break;
            case RDS_BFM_CLEANUP: // Очистка
              delete data;
              break;
            case RDS_BFM_SETUP: // Настройка
              return data->Setup();
            case RDS_BFM_SAVEBIN: // Сохранение параметров
              data->SaveBin();
              break;
            case RDS_BFM_LOADBIN: // Загрузка параметров
              return data->LoadBin();
            case RDS_BFM_MENUFUNCTION:// Выбор пункта меню
              rdsOpenSystemWindow(BlockData->Parent);
              break;

            case RDS_BFM_MANUALINSERT: // Добавление блока пользователем
              if( ((RDS_PMANUALINSERTDATA)ExtParam)->Single )
                { if(data->Setup()) // Параметры изменены
                    rdsSetModifiedFlag(TRUE);// Взвести флаг изменений
                }
              break;
          }
        return RDS_BFR_DONE;
      }
      //=========================================

      //=========================================================================
      // Реакция на удаление блока
      //=========================================================================

      //=========== Блок управления =============
      // Структура личной области данных блока
      typedef struct
      { // Указатель на структуру подписки на скорость
        RDS_PDYNVARLINK VLink;
        // Указатель на структуру подписки на направление
        RDS_PDYNVARLINK ALink;
      } TestBlkMoveSetterData;
      //=========================================

      // Функция обратного вызова для подсчета подписчиков
      // динамической переменной
      BOOL RDSCALL DynVarUsersCountEnum(RDS_BHANDLE /*block*/,
          RDS_PDYNVARLINK /*link*/,LPVOID ptr)
      { // Приводим ptr к типу "указатель на целое"
        int *pCount=(int*)ptr;
        (*pCount)++; // Увеличиваем счетчик
        return TRUE;
      }
      //=========================================
      // Подсчет числа подписчиков на динамическую переменную
      int CountDynVarUsers(RDS_PDYNVARLINK link)
      { int count=0;
        // Перебрать всех подписчиков переменной link
        rdsEnumDynVarSubscribers(link,DynVarUsersCountEnum,&count);
        return count;
      }
      //=========================================

      // Модель блока управления
      extern "C" __declspec(dllexport)
        int RDSCALL TestBlkMoveSetter(int CallMode,
                                      RDS_PBLOCKDATA BlockData,
                                      LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define Speed (*((double *)(pStart+2*RDS_VSZ_S)))
      #define Angle (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
        // Вспомогательная переменная – указатель на личную область
        // данных блока, приведенный к правильному типу
        TestBlkMoveSetterData *privdata;

        switch(CallMode)
          { // Инициализация блока
            case RDS_BFM_INIT:
              // Отведение памяти под личную область данных
              privdata=(TestBlkMoveSetterData*)
                        malloc(sizeof(TestBlkMoveSetterData));
              BlockData->BlockData=privdata;
              // Создание переменной для передачи скорости
              privdata->VLink=rdsCreateAndSubscribeDV(
                                RDS_DVPARENT,   // В родительской
                                "BlkMove_Speed",// Имя переменной
                                "D",            // Тип double
                                TRUE,           // Запрет удаления
                                NULL);          // Без нач.значения
              // Создание переменной для передачи направления
              privdata->ALink=rdsCreateAndSubscribeDV(
                                RDS_DVPARENT,
                                "BlkMove_Angle",
                                "D",
                                TRUE,
                                NULL);
              break;

            // Очистка данных блока
            case RDS_BFM_CLEANUP:
              // Приведение указателя на личную область данных к
              // правильному типу
              privdata=(TestBlkMoveSetterData*)(BlockData->BlockData);
              // Удаление динамических переменных
              rdsDeleteDVByLink(privdata->VLink);
              rdsDeleteDVByLink(privdata->ALink);
              // Освобождение отведенной памяти
              free(privdata);
              break;

            // Проверка типа статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSDD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              // Приведение указателя на личную область данных к
              // правильному типу
              privdata=(TestBlkMoveSetterData*)(BlockData->BlockData);
              // Проверка существования переменной направления
              if(privdata->ALink!=NULL && privdata->ALink->Data!=NULL)
                { // Переменная существует – привести к типу double*
                  double *pa=(double*)privdata->ALink->Data;
                  if(*pa!=Angle) // Значение направления изменилось
                    { // Записать значение в динамическую переменную
                      *pa=Angle;
                      // Уведомить всех подписчиков об изменении
                      rdsNotifyDynVarSubscribers(privdata->ALink);
                    }
                }
              // Проверка существования переменной скорости
              if(privdata->VLink!=NULL && privdata->VLink->Data!=NULL)
                { // Переменная существует – привести к типу double*
                  double *pv=(double*)privdata->VLink->Data;
                  if(*pv!=Speed)
                    { // Записать значение в динамическую переменную
                      *pv=Speed;
                      // Уведомить всех подписчиков об изменении
                      rdsNotifyDynVarSubscribers(privdata->VLink);
                    }
                }
              break;

            // Удаление блока пользователем
            case RDS_BFM_MANUALDELETE:
              // Приведение указателя на личную область данных к
              // правильному типу
              privdata=(TestBlkMoveSetterData*)(BlockData->BlockData);
              // Если блок удаляется в составе подсистемы,
              // предупреждать не нужно
              if( ((RDS_PMANUALDELETEDATA)ExtParam)->WithSys)
                break;
              // Считаем число подписчиков на переменные этого блока
              if(CountDynVarUsers(privdata->VLink)>1 ||
                 CountDynVarUsers(privdata->ALink)>1)
                // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
                //   в противном случае необходимо использовать версии функций
                //   с суффиксом "W" и символьные константы с префиксом "L"
                rdsMessageBox("Внимание! Этот блок предоставляет "
                              "данные другим блокам. Его удаление "
                              "приведет к их отключению.",
                              BlockData->BlockName,
                              MB_OK | MB_ICONWARNING);
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef Angle
      #undef Speed
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //============ Подвижный блок =============
      // Структура личной области данных блока
      typedef struct
      { // Указатель на структуру подписки на скорость
        RDS_PDYNVARLINK VLink;
        // Указатель на структуру подписки на направление
        RDS_PDYNVARLINK ALink;
        // Указатель на структуру подписки на время
        RDS_PDYNVARLINK Time;

        // Дополнительные поля для хранения значений синуса
        // и косинуса направления движения блока
        double SinA;
        double CosA;
      } TestBlkMoveObjectData;

      // Модель перемещающегося блока
      extern "C" __declspec(dllexport)
        int RDSCALL TestBlkMoveObject(int CallMode,
                                      RDS_PBLOCKDATA BlockData,
                                      LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
      #define a (*((double *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_D)))
      #define t0 (*((double *)(pStart+2*RDS_VSZ_S+3*RDS_VSZ_D)))
        // Вспомогательная переменная – указатель на личную область
        // данных блока, приведенный к правильному типу
        TestBlkMoveObjectData *privdata;
        // Вспомогательные переменные для скорости и времени
        double v,time;

        switch(CallMode)
          { // Инициализация блока
            case RDS_BFM_INIT:
              // Отведение памяти под личную область данных
              privdata=(TestBlkMoveObjectData*)
                        malloc(sizeof(TestBlkMoveObjectData));
              BlockData->BlockData=privdata;
              // Подписка на переменную скорости
              privdata->VLink=rdsSubscribeToDynamicVar(
                              RDS_DVPARENT,    // В родительской
                              "BlkMove_Speed", // Имя переменной
                              "D",             // Тип double
                              FALSE);          // Без поиска
              // Подписка на переменную направления
              privdata->ALink=rdsSubscribeToDynamicVar(
                              RDS_DVPARENT,    // В родительской
                              "BlkMove_Angle", // Имя переменной
                              "D",             // Тип double
                              FALSE);          // Без поиска
              // Подписка на переменную времени
              privdata->Time=rdsSubscribeToDynamicVar(
                              RDS_DVPARENT,    // В родительской
                              "DynTime",       // Имя переменной
                              "D",             // Тип double
                              TRUE);           // Поиск в иерархии
              break;

            // Очистка данных блока
            case RDS_BFM_CLEANUP:
              // Приведение указателя на личную область данных к правильному типу
              privdata=(TestBlkMoveObjectData*)(BlockData->BlockData);
              // Прекращение подписки на все динамические переменные
              rdsUnsubscribeFromDynamicVar(privdata->VLink);
              rdsUnsubscribeFromDynamicVar(privdata->ALink);
              rdsUnsubscribeFromDynamicVar(privdata->Time);
              // Освобождение отведенной памяти
              free(privdata);
              break;

            // Проверка типа статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSDDDD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Реакция на изменение динамической переменной
            case RDS_BFM_DYNVARCHANGE:
              // Приведение указателя на личную область данных к правильному типу
              privdata=(TestBlkMoveObjectData*)(BlockData->BlockData);
              // Проверка – удалось ли подписаться на все переменные
              // (если хотя бы один указатель – NULL, значит, не удалось)
              if(privdata->VLink==NULL || // Скорость
                 privdata->ALink==NULL || // Направление
                 privdata->Time==NULL)    // Время
                break;
              // Проверка – существуют ли все переменные
              // (если хотя бы один указатель – NULL, значит, не удалось)
              if(privdata->VLink->Data==NULL || // Скорость
                 privdata->ALink->Data==NULL || // Направление
                 privdata->Time->Data==NULL)    // Время
                break;
              // Если изменилась переменная направления, нужно вычислить
              // и запомнить новые значения ыинуса и косинуса угла
              if(ExtParam==(void*)(privdata->ALink))
                { // Изменилось направление – привести к типу double*
                  double *pa=(double*)privdata->ALink->Data;
                  // Значение угла в радианах
                  a=(*pa)*M_PI/180.0;
                  // Значения синуса и косинуса
                  privdata->SinA=sin(a);
                  privdata->CosA=cos(a);
                }
              // Записать во вспомогательные переменные v и time значения
              // скорости и времени
              v=*(double*)privdata->VLink->Data;
              time=*(double*)privdata->Time->Data;
              // Вычислить новое смещение изображения блока
              x+=v*(time-t0)*privdata->CosA;
              y-=v*(time-t0)*privdata->SinA;
              // Запомнить значение времени, для которого вычислены
              // новые координаты
              t0=time;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef t0
      #undef a
      #undef y
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //============================================================================
      // Реакция на изменение размеров блока (в двухкоординатной рукоятке)
      //============================================================================

      //====== Класс личной области данных ======
      class TSimpleJoystick
      { private:
          // Центр круга (рукоятки) до начала перетаскивания
          int OldHandleX,OldHandleY;
          // Координаты курсора на момент начала перетаскивания
          int OldMouseX,OldMouseY;
          // Флаги фиксации одной из координат
          BOOL LockX,LockY;
          // Идентификаторы добавленных пунктов меню
          RDS_MENUITEM MenuLockX,MenuLockY;
        public:
          // Настроечные параметры блока
          COLORREF BorderColor;       // Цвет рамки блока
          COLORREF FieldColor;        // Цвет прямоугольника
          COLORREF HandleColor;       // Цвет круга в покое
          COLORREF MovingHandleColor; // Цвет круга при таскании
          COLORREF GrayedColor;       // Цвет недоступной области
          int HandleSize;             // Диаметр круга

          // Реакция на нажатие кнопки мыши
          int MouseDown(RDS_PMOUSEDATA mouse,double x,double y,DWORD *pFlags);
          // Реакция на перемещение курсора мыши
          void MouseMove(RDS_PMOUSEDATA mouse,double *px,double *py);
          // Рисование изображения блока
          void Draw(RDS_PDRAWDATA draw,double x,double y,BOOL moving);
          // Реакция на выбор добавленного пункта меню
          void MenuFunction(RDS_PMENUFUNCDATA MenuData);

          // Реакция на изменение размеров блока
          void Resizing(RDS_PRESIZEDATA ResData);
          // Конструктор класса
          TSimpleJoystick(void)
            { BorderColor=0;          // Черная рамка
              FieldColor=0xffffff;    // Белое поле
              HandleColor=0xff0000;   // Синий круг
              MovingHandleColor=0xff; // Красный при таскании
              HandleSize=20;          // Диаметр круга
              GrayedColor=0x7f7f7f;   // Серый
              LockX=LockY=FALSE;      // Фиксация выключена
              // Создание пунктов меню
              // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
              //   в противном случае необходимо использовать версии функций
              //   с суффиксом "W" и символьные константы с префиксом "L"
              MenuLockX=rdsRegisterContextMenuItem("Фиксировать X",1,0);
              MenuLockY=rdsRegisterContextMenuItem("Фиксировать Y",2,0);

            };
          // Деструктор класса
          ~TSimpleJoystick()
            { // Уничтожение пунктов меню
              rdsUnregisterMenuItem(MenuLockX);
              rdsUnregisterMenuItem(MenuLockY);
            };
      };
      //=========================================

      // Рисование изображения блока
      void TSimpleJoystick::Draw(RDS_PDRAWDATA draw,
                                 double x,double y,BOOL moving)
      { int hx,hy,cx,cy;
        RECT r;
        int hR=HandleSize*draw->DoubleZoom/2; // Радиус круга-рукоятки

        // Если размер блока - нулевой, рисовать негде
        if(draw->Height==0 || draw->Width==0)
          return;

        // Рисование поля блока
        rdsXGSetPenStyle(0,PS_SOLID,1,BorderColor,R2_COPYPEN);
        rdsXGSetBrushStyle(0,RDS_GFS_SOLID,FieldColor);
        rdsXGRectangle(draw->Left,draw->Top,
                       draw->Left+draw->Width,draw->Top+draw->Height);

        // Вычисление центра прямоугольника блока
        cx=draw->Left+draw->Width/2;
        cy=draw->Top+draw->Height/2;

        // Вычисление координат центра круга-рукоятки
        hx=cx+x*draw->Width/2;
        hy=cy-y*draw->Height/2;

        // Установка области отсечения
        r.left=draw->Left+1;
        r.top=draw->Top+1;
        r.right=draw->Left+draw->Width-1;
        r.bottom=draw->Top+draw->Height-1;
        rdsXGSetClipRect(&r);

        // Рисование ограничений
        if(LockX||LockY) // Фиксируется одна из координат
          { // Установка серого цвета заливки
            rdsXGSetBrushStyle(0,RDS_GFS_SOLID,GrayedColor);
            if(LockX) // Фиксируется X
              { rdsXGFillRect(r.left,r.top,hx-hR,r.bottom);  // Слева
                rdsXGFillRect(hx+hR,r.top,r.right,r.bottom); // Справа
              }
            else // Фиксируется Y
              { rdsXGFillRect(r.left,r.top,r.right,hy-hR);   // Сверху
                rdsXGFillRect(r.left,hy+hR,r.right,r.bottom);// Снизу
              }
          }

        // Линии перекрестия
        rdsXGMoveTo(cx,draw->Top);
        rdsXGLineTo(cx,draw->Top+draw->Height);
        rdsXGMoveTo(draw->Left,cy);
        rdsXGLineTo(draw->Left+draw->Width,cy);

        // Рисование круга (цвет зависит от параметра moving)
        rdsXGSetPenStyle(RDS_GFSTYLE,PS_NULL,0,0,0);
        rdsXGSetBrushStyle(0,RDS_GFS_SOLID,
                           moving?MovingHandleColor:HandleColor);
        rdsXGEllipse(hx-hR,hy-hR,hx+hR+1,hy+hR+1);

        // Отмена отсечения
        rdsXGSetClipRect(NULL);
      }
      //=========================================

      // Реакция на нажатие кнопки мыши
      int TSimpleJoystick::MouseDown(RDS_PMOUSEDATA mouse,
                                     double x,double y,DWORD *pFlags)
      { int hx,hy,cx,cy,
            hR=HandleSize*mouse->DoubleZoom/2; // Радиус круга

        // Если размер - нулевой, реакция не имеет смысла
        if(mouse->Height==0 || mouse->Width==0)
          return RDS_BFR_DONE;
        // Если нажата не левая кнопка, перетаскивать не надо
        // Разрешаем в этом случае вызов контекстного меню блока
        if(mouse->Button!=RDS_MLEFTBUTTON)
          return RDS_BFR_SHOWMENU;

        // Координаты цента блока
        cx=mouse->Left+mouse->Width/2;
        cy=mouse->Top+mouse->Height/2;
        // Координаты центра круга-рукоятки
        hx=cx+x*mouse->Width/2;
        hy=cy-y*mouse->Height/2;

        // Проверка попадания курсора в круг
        if(abs(mouse->x-hx)<=hR && abs(mouse->y-hy)<=hR)
          { // Курсор попал в круг
            // Запоминаем координаты центр круга на момент
            // начала перетаскивания
            OldHandleX=hx;
            OldHandleY=hy;
            // Координаты курсора на начало перетаскивания
            OldMouseX=mouse->x;
            OldMouseY=mouse->y;
            // Взводим флаг захвата мыши
            *pFlags|=RDS_MOUSECAPTURE;
          }
        // Курсор не попал в рукоятку - захватывать мышь
        // и подготавливать перетаскивание не нужно
        return RDS_BFR_DONE;
      }
      //=========================================

      // Реакция на перемещение курсора мыши
      void TSimpleJoystick::MouseMove(RDS_PMOUSEDATA mouse,
                                      double *px,double *py)
      { int hx,hy,cx,cy;

        // Если размер - нулевой, реакция не имеет смысла
        if(mouse->Height==0 || mouse->Width==0)
          { *px=*py=0.0;
            return;
          }

        // Новые координаты центра рукоятки
        hx=OldHandleX+(mouse->x-OldMouseX);
        hy=OldHandleY+(mouse->y-OldMouseY);

        // Координаты центра блока
        cx=mouse->Left+mouse->Width/2;
        cy=mouse->Top+mouse->Height/2;

        // По новым координатам центра рукоятки вычисляем соответствующие
        // им вещественные значения выходов, ограничивая их
        // диапазоном [-1...1]
        if(!LockX)
          { *px=2.0*(hx-cx)/mouse->Width;
            if(*px>1.0) *px=1.0;
            else if(*px<-1.0) *px=-1.0;
          }
        if(!LockY)
          { *py=-2.0*(hy-cy)/mouse->Height;
            if(*py>1.0) *py=1.0;
            else if(*py<-1.0) *py=-1.0;
          }
      }
      //=========================================

      // Функция реакции на выбор одного из пунктов меню
      void TSimpleJoystick::MenuFunction(RDS_PMENUFUNCDATA MenuData)
      { switch(MenuData->Function)
          { case 1: // Выбран пункт "Фиксировать X"
              LockX=!LockX; // Переключаем флаг фиксации X
              LockY=FALSE;  // Отключаем фиксацию Y
              break;
            case 2: // Выбран пункт "Фиксировать Y"
              LockY=!LockY; // Переключаем флаг фиксации Y
              LockX=FALSE;  // Отключаем фиксацию X
              break;
          }
        // Установка галочек у пунктов меню в зависимости от
        // флагов фиксации координат
        rdsSetMenuItemOptions(MenuLockX,LockX?RDS_MENU_CHECKED:0);
        rdsSetMenuItemOptions(MenuLockY,LockY?RDS_MENU_CHECKED:0);
      }
      //=========================================

      // Реакция на изменение размеров блока
      void TSimpleJoystick::Resizing(RDS_PRESIZEDATA ResData)
      {
        if(ResData->HorzResize && (!ResData->VertResize))
          // Перетаскивается левый или правый маркер
          ResData->newHeight=ResData->newWidth;
        else if((!ResData->HorzResize) && ResData->VertResize)
          // Перетаскивается верхний или нижний маркер
          ResData->newWidth=ResData->newHeight;
        else // Перетаскивается угловой маркер или размер задан точно
          { // Вычисляем среднее арифметическое
            int avg=(ResData->newWidth+ResData->newHeight)/2;
            // Присваиваем его ширине и высоте
            ResData->newWidth=ResData->newHeight=avg;
          }
      }
      //=========================================
      // Функция модели блока
      extern "C" __declspec(dllexport)
        int RDSCALL SimpleJoystick(int CallMode,
                RDS_PBLOCKDATA BlockData,
                LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
        // Вспомогательная переменная - указатель на личную область,
        //приведенный к правильному типу
        TSimpleJoystick *data=(TSimpleJoystick*)(BlockData->BlockData);
        switch(CallMode)
          { // Инициализация
           case RDS_BFM_INIT:
              BlockData->BlockData=new TSimpleJoystick();
              break;
            // Очистка
            case RDS_BFM_CLEANUP:
              delete data;
              break;
            // Проверка допустимости типов переменных
            case RDS_BFM_VARCHECK:
              return strcmp((char*)ExtParam,"{SSDD}")?
                RDS_BFR_BADVARSMSG:RDS_BFR_DONE;
            // Нажатие кнопки мыши
            case RDS_BFM_MOUSEDOWN:
              return data->MouseDown((RDS_PMOUSEDATA)ExtParam,x,y,
                &(BlockData->Flags));
            // Отпускание кнопки мыши
            case RDS_BFM_MOUSEUP:
              // Снятие захвата мыши
              RDS_SETFLAG(BlockData->Flags,RDS_MOUSECAPTURE,FALSE);
              break;
            // Перемещение курсора мыши
            case RDS_BFM_MOUSEMOVE:
              // Проверка: включен ли захват мыши
              if(BlockData->Flags & RDS_MOUSECAPTURE)	// Включен
                { // Вызываем функцию реакции
                  data->MouseMove((RDS_PMOUSEDATA)ExtParam,&x,&y);
                  Ready=1; // Взводим сигнал готовности
                }
              break;
            // Рисование
            case RDS_BFM_DRAW:
              data->Draw((RDS_PDRAWDATA)ExtParam,x,y,
                         BlockData->Flags & RDS_MOUSECAPTURE);
              break;

            // Выбор пользователем добавленного пункта меню
            case RDS_BFM_MENUFUNCTION:
              data->MenuFunction((RDS_PMENUFUNCDATA)ExtParam);
              break;

            // Изменение размеров блока
            case RDS_BFM_RESIZE:
            case RDS_BFM_RESIZING:
              data->Resizing((RDS_PRESIZEDATA)ExtParam);
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================